Make WordPress Core

Ticket #23673: 23673.diff

File 23673.diff, 939.7 KB (added by wonderboymusic, 12 years ago)
  • wp-admin/includes/image.php

    diff --git wp-admin/includes/image.php wp-admin/includes/image.php
    index a842287..34e726c 100644
    function wp_generate_attachment_metadata( $attachment_id, $file ) { 
    116116                if ( $image_meta )
    117117                        $metadata['image_meta'] = $image_meta;
    118118
     119        } elseif ( preg_match( '#^video/#', get_post_mime_type( $attachment ) ) ) {
     120                $metadata = wp_read_video_metadata( $file );
     121        } elseif ( preg_match( '#^audio/#', get_post_mime_type( $attachment ) ) ) {
     122                $metadata = wp_read_audio_metadata( $file );
    119123        }
    120124
    121125        return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id );
  • new file wp-includes/ID3/class-getid3.php

    diff --git wp-includes/ID3/class-getid3.php wp-includes/ID3/class-getid3.php
    new file mode 100644
    index 0000000..8c18163
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7//                                                             //
     8// Please see readme.txt for more information                  //
     9//                                                            ///
     10/////////////////////////////////////////////////////////////////
     11
     12// define a constant rather than looking up every time it is needed
     13if (!defined('GETID3_OS_ISWINDOWS')) {
     14        define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0));
     15}
     16// Get base path of getID3() - ONCE
     17if (!defined('GETID3_INCLUDEPATH')) {
     18        define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
     19}
     20
     21// attempt to define temp dir as something flexible but reliable
     22$temp_dir = ini_get('upload_tmp_dir');
     23if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
     24        $temp_dir = '';
     25}
     26if (!$temp_dir && function_exists('sys_get_temp_dir')) {
     27        // PHP v5.2.1+
     28        // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
     29        $temp_dir = sys_get_temp_dir();
     30}
     31$temp_dir = realpath($temp_dir);
     32$open_basedir = ini_get('open_basedir');
     33if ($open_basedir) {
     34        // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
     35        $temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
     36        $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
     37        if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
     38                $temp_dir .= DIRECTORY_SEPARATOR;
     39        }
     40        $found_valid_tempdir = false;
     41        $open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
     42        foreach ($open_basedirs as $basedir) {
     43                if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
     44                        $basedir .= DIRECTORY_SEPARATOR;
     45                }
     46                if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
     47                        $found_valid_tempdir = true;
     48                        break;
     49                }
     50        }
     51        if (!$found_valid_tempdir) {
     52                $temp_dir = '';
     53        }
     54        unset($open_basedirs, $found_valid_tempdir, $basedir);
     55}
     56if (!$temp_dir) {
     57        $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
     58}
     59// $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
     60define('GETID3_TEMP_DIR', $temp_dir);
     61unset($open_basedir, $temp_dir);
     62
     63// End: Defines
     64
     65
     66class getID3
     67{
     68        // public: Settings
     69        public $encoding        = 'UTF-8';        // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
     70        public $encoding_id3v1  = 'ISO-8859-1';   // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
     71
     72        // public: Optional tag checks - disable for speed.
     73        public $option_tag_id3v1         = true;  // Read and process ID3v1 tags
     74        public $option_tag_id3v2         = true;  // Read and process ID3v2 tags
     75        public $option_tag_lyrics3       = true;  // Read and process Lyrics3 tags
     76        public $option_tag_apetag        = true;  // Read and process APE tags
     77        public $option_tags_process      = true;  // Copy tags to root key 'tags' and encode to $this->encoding
     78        public $option_tags_html         = true;  // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
     79
     80        // public: Optional tag/comment calucations
     81        public $option_extra_info        = true;  // Calculate additional info such as bitrate, channelmode etc
     82
     83        // public: Optional handling of embedded attachments (e.g. images)
     84        public $option_save_attachments  = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
     85
     86        // public: Optional calculations
     87        public $option_md5_data          = false; // Get MD5 sum of data part - slow
     88        public $option_md5_data_source   = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
     89        public $option_sha1_data         = false; // Get SHA1 sum of data part - slow
     90        public $option_max_2gb_check     = null;  // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX)
     91
     92        // public: Read buffer size in bytes
     93        public $option_fread_buffer_size = 32768;
     94
     95        // Public variables
     96        public $filename;                         // Filename of file being analysed.
     97        public $fp;                               // Filepointer to file being analysed.
     98        public $info;                             // Result array.
     99        public $tempdir = GETID3_TEMP_DIR;
     100
     101        // Protected variables
     102        protected $startup_error   = '';
     103        protected $startup_warning = '';
     104        protected $memory_limit    = 0;
     105
     106        const VERSION           = '1.9.5-20130220';
     107        const FREAD_BUFFER_SIZE = 32768;
     108
     109        const ATTACHMENTS_NONE   = false;
     110        const ATTACHMENTS_INLINE = true;
     111
     112        // public: constructor
     113        public function __construct() {
     114
     115                // Check for PHP version
     116                $required_php_version = '5.0.5';
     117                if (version_compare(PHP_VERSION, $required_php_version, '<')) {
     118                        $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION;
     119                        return false;
     120                }
     121
     122                // Check memory
     123                $this->memory_limit = ini_get('memory_limit');
     124                if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
     125                        // could be stored as "16M" rather than 16777216 for example
     126                        $this->memory_limit = $matches[1] * 1048576;
     127                } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
     128                        // could be stored as "2G" rather than 2147483648 for example
     129                        $this->memory_limit = $matches[1] * 1073741824;
     130                }
     131                if ($this->memory_limit <= 0) {
     132                        // memory limits probably disabled
     133                } elseif ($this->memory_limit <= 4194304) {
     134                        $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
     135                } elseif ($this->memory_limit <= 12582912) {
     136                        $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini';
     137                }
     138
     139                // Check safe_mode off
     140                if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
     141                        $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
     142                }
     143
     144                if (intval(ini_get('mbstring.func_overload')) > 0) {
     145                        $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.');
     146                }
     147
     148                // Check for magic_quotes_runtime
     149                if (function_exists('get_magic_quotes_runtime')) {
     150                        if (get_magic_quotes_runtime()) {
     151                                return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).');
     152                        }
     153                }
     154
     155                // Check for magic_quotes_gpc
     156                if (function_exists('magic_quotes_gpc')) {
     157                        if (get_magic_quotes_gpc()) {
     158                                return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).');
     159                        }
     160                }
     161
     162                // Load support library
     163                if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
     164                        $this->startup_error .= 'getid3.lib.php is missing or corrupt';
     165                }
     166
     167                if ($this->option_max_2gb_check === null) {
     168                        $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
     169                }
     170
     171
     172                // Needed for Windows only:
     173                // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
     174                //   as well as other helper functions such as head, tail, md5sum, etc
     175                // This path cannot contain spaces, but the below code will attempt to get the
     176                //   8.3-equivalent path automatically
     177                // IMPORTANT: This path must include the trailing slash
     178                if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
     179
     180                        $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
     181
     182                        if (!is_dir($helperappsdir)) {
     183                                $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
     184                        } elseif (strpos(realpath($helperappsdir), ' ') !== false) {
     185                                $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
     186                                $path_so_far = array();
     187                                foreach ($DirPieces as $key => $value) {
     188                                        if (strpos($value, ' ') !== false) {
     189                                                if (!empty($path_so_far)) {
     190                                                        $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
     191                                                        $dir_listing = `$commandline`;
     192                                                        $lines = explode("\n", $dir_listing);
     193                                                        foreach ($lines as $line) {
     194                                                                $line = trim($line);
     195                                                                if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
     196                                                                        list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
     197                                                                        if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
     198                                                                                $value = $shortname;
     199                                                                        }
     200                                                                }
     201                                                        }
     202                                                } else {
     203                                                        $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.';
     204                                                }
     205                                        }
     206                                        $path_so_far[] = $value;
     207                                }
     208                                $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
     209                        }
     210                        define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
     211                }
     212
     213                return true;
     214        }
     215
     216        public function version() {
     217                return self::VERSION;
     218        }
     219
     220        public function fread_buffer_size() {
     221                return $this->option_fread_buffer_size;
     222        }
     223
     224        // public: setOption
     225        public function setOption($optArray) {
     226                if (!is_array($optArray) || empty($optArray)) {
     227                        return false;
     228                }
     229                foreach ($optArray as $opt => $val) {
     230                        if (isset($this->$opt) === false) {
     231                                continue;
     232                        }
     233                        $this->$opt = $val;
     234                }
     235                return true;
     236        }
     237
     238        public function openfile($filename) {
     239                try {
     240                        if (!empty($this->startup_error)) {
     241                                throw new getid3_exception($this->startup_error);
     242                        }
     243                        if (!empty($this->startup_warning)) {
     244                                $this->warning($this->startup_warning);
     245                        }
     246
     247                        // init result array and set parameters
     248                        $this->filename = $filename;
     249                        $this->info = array();
     250                        $this->info['GETID3_VERSION']   = $this->version();
     251                        $this->info['php_memory_limit'] = $this->memory_limit;
     252
     253                        // remote files not supported
     254                        if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
     255                                throw new getid3_exception('Remote files are not supported - please copy the file locally first');
     256                        }
     257
     258                        $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
     259                        $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename);
     260
     261                        // open local file
     262                        if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
     263                                // great
     264                        } else {
     265                                throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)');
     266                        }
     267
     268                        $this->info['filesize'] = filesize($filename);
     269                        // set redundant parameters - might be needed in some include file
     270                        $this->info['filename']     = basename($filename);
     271                        $this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
     272                        $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
     273
     274
     275                        // option_max_2gb_check
     276                        if ($this->option_max_2gb_check) {
     277                                // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
     278                                // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
     279                                // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
     280                                $fseek = fseek($this->fp, 0, SEEK_END);
     281                                if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
     282                                        ($this->info['filesize'] < 0) ||
     283                                        (ftell($this->fp) < 0)) {
     284                                                $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);
     285
     286                                                if ($real_filesize === false) {
     287                                                        unset($this->info['filesize']);
     288                                                        fclose($this->fp);
     289                                                        throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.');
     290                                                } elseif (getid3_lib::intValueSupported($real_filesize)) {
     291                                                        unset($this->info['filesize']);
     292                                                        fclose($this->fp);
     293                                                        throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org');
     294                                                }
     295                                                $this->info['filesize'] = $real_filesize;
     296                                                $this->error('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.');
     297                                }
     298                        }
     299
     300                        // set more parameters
     301                        $this->info['avdataoffset']        = 0;
     302                        $this->info['avdataend']           = $this->info['filesize'];
     303                        $this->info['fileformat']          = '';                // filled in later
     304                        $this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
     305                        $this->info['video']['dataformat'] = '';                // filled in later, unset if not used
     306                        $this->info['tags']                = array();           // filled in later, unset if not used
     307                        $this->info['error']               = array();           // filled in later, unset if not used
     308                        $this->info['warning']             = array();           // filled in later, unset if not used
     309                        $this->info['comments']            = array();           // filled in later, unset if not used
     310                        $this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
     311
     312                        return true;
     313
     314                } catch (Exception $e) {
     315                        $this->error($e->getMessage());
     316                }
     317                return false;
     318        }
     319
     320        // public: analyze file
     321        public function analyze($filename) {
     322                try {
     323                        if (!$this->openfile($filename)) {
     324                                return $this->info;
     325                        }
     326
     327                        // Handle tags
     328                        foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
     329                                $option_tag = 'option_tag_'.$tag_name;
     330                                if ($this->$option_tag) {
     331                                        $this->include_module('tag.'.$tag_name);
     332                                        try {
     333                                                $tag_class = 'getid3_'.$tag_name;
     334                                                $tag = new $tag_class($this);
     335                                                $tag->Analyze();
     336                                        }
     337                                        catch (getid3_exception $e) {
     338                                                throw $e;
     339                                        }
     340                                }
     341                        }
     342                        if (isset($this->info['id3v2']['tag_offset_start'])) {
     343                                $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
     344                        }
     345                        foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
     346                                if (isset($this->info[$tag_key]['tag_offset_start'])) {
     347                                        $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
     348                                }
     349                        }
     350
     351                        // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
     352                        if (!$this->option_tag_id3v2) {
     353                                fseek($this->fp, 0, SEEK_SET);
     354                                $header = fread($this->fp, 10);
     355                                if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
     356                                        $this->info['id3v2']['header']        = true;
     357                                        $this->info['id3v2']['majorversion']  = ord($header{3});
     358                                        $this->info['id3v2']['minorversion']  = ord($header{4});
     359                                        $this->info['avdataoffset']          += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
     360                                }
     361                        }
     362
     363                        // read 32 kb file data
     364                        fseek($this->fp, $this->info['avdataoffset'], SEEK_SET);
     365                        $formattest = fread($this->fp, 32774);
     366
     367                        // determine format
     368                        $determined_format = $this->GetFileFormat($formattest, $filename);
     369
     370                        // unable to determine file format
     371                        if (!$determined_format) {
     372                                fclose($this->fp);
     373                                return $this->error('unable to determine file format');
     374                        }
     375
     376                        // check for illegal ID3 tags
     377                        if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
     378                                if ($determined_format['fail_id3'] === 'ERROR') {
     379                                        fclose($this->fp);
     380                                        return $this->error('ID3 tags not allowed on this file type.');
     381                                } elseif ($determined_format['fail_id3'] === 'WARNING') {
     382                                        $this->warning('ID3 tags not allowed on this file type.');
     383                                }
     384                        }
     385
     386                        // check for illegal APE tags
     387                        if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
     388                                if ($determined_format['fail_ape'] === 'ERROR') {
     389                                        fclose($this->fp);
     390                                        return $this->error('APE tags not allowed on this file type.');
     391                                } elseif ($determined_format['fail_ape'] === 'WARNING') {
     392                                        $this->warning('APE tags not allowed on this file type.');
     393                                }
     394                        }
     395
     396                        // set mime type
     397                        $this->info['mime_type'] = $determined_format['mime_type'];
     398
     399                        // supported format signature pattern detected, but module deleted
     400                        if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
     401                                fclose($this->fp);
     402                                return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
     403                        }
     404
     405                        // module requires iconv support
     406                        // Check encoding/iconv support
     407                        if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
     408                                $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
     409                                if (GETID3_OS_ISWINDOWS) {
     410                                        $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32';
     411                                } else {
     412                                        $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
     413                                }
     414                                return $this->error($errormessage);
     415                        }
     416
     417                        // include module
     418                        include_once(GETID3_INCLUDEPATH.$determined_format['include']);
     419
     420                        // instantiate module class
     421                        $class_name = 'getid3_'.$determined_format['module'];
     422                        if (!class_exists($class_name)) {
     423                                return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
     424                        }
     425                        $class = new $class_name($this);
     426                        $class->Analyze();
     427                        unset($class);
     428
     429                        // close file
     430                        fclose($this->fp);
     431
     432                        // process all tags - copy to 'tags' and convert charsets
     433                        if ($this->option_tags_process) {
     434                                $this->HandleAllTags();
     435                        }
     436
     437                        // perform more calculations
     438                        if ($this->option_extra_info) {
     439                                $this->ChannelsBitratePlaytimeCalculations();
     440                                $this->CalculateCompressionRatioVideo();
     441                                $this->CalculateCompressionRatioAudio();
     442                                $this->CalculateReplayGain();
     443                                $this->ProcessAudioStreams();
     444                        }
     445
     446                        // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
     447                        if ($this->option_md5_data) {
     448                                // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
     449                                if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
     450                                        $this->getHashdata('md5');
     451                                }
     452                        }
     453
     454                        // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
     455                        if ($this->option_sha1_data) {
     456                                $this->getHashdata('sha1');
     457                        }
     458
     459                        // remove undesired keys
     460                        $this->CleanUp();
     461
     462                } catch (Exception $e) {
     463                        $this->error('Caught exception: '.$e->getMessage());
     464                }
     465
     466                // return info array
     467                return $this->info;
     468        }
     469
     470
     471        // private: error handling
     472        public function error($message) {
     473                $this->CleanUp();
     474                if (!isset($this->info['error'])) {
     475                        $this->info['error'] = array();
     476                }
     477                $this->info['error'][] = $message;
     478                return $this->info;
     479        }
     480
     481
     482        // private: warning handling
     483        public function warning($message) {
     484                $this->info['warning'][] = $message;
     485                return true;
     486        }
     487
     488
     489        // private: CleanUp
     490        private function CleanUp() {
     491
     492                // remove possible empty keys
     493                $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
     494                foreach ($AVpossibleEmptyKeys as $dummy => $key) {
     495                        if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
     496                                unset($this->info['audio'][$key]);
     497                        }
     498                        if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
     499                                unset($this->info['video'][$key]);
     500                        }
     501                }
     502
     503                // remove empty root keys
     504                if (!empty($this->info)) {
     505                        foreach ($this->info as $key => $value) {
     506                                if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
     507                                        unset($this->info[$key]);
     508                                }
     509                        }
     510                }
     511
     512                // remove meaningless entries from unknown-format files
     513                if (empty($this->info['fileformat'])) {
     514                        if (isset($this->info['avdataoffset'])) {
     515                                unset($this->info['avdataoffset']);
     516                        }
     517                        if (isset($this->info['avdataend'])) {
     518                                unset($this->info['avdataend']);
     519                        }
     520                }
     521
     522                // remove possible duplicated identical entries
     523                if (!empty($this->info['error'])) {
     524                        $this->info['error'] = array_values(array_unique($this->info['error']));
     525                }
     526                if (!empty($this->info['warning'])) {
     527                        $this->info['warning'] = array_values(array_unique($this->info['warning']));
     528                }
     529
     530                // remove "global variable" type keys
     531                unset($this->info['php_memory_limit']);
     532
     533                return true;
     534        }
     535
     536
     537        // return array containing information about all supported formats
     538        public function GetFileFormatArray() {
     539                static $format_info = array();
     540                if (empty($format_info)) {
     541                        $format_info = array(
     542
     543                                // Audio formats
     544
     545                                // AC-3   - audio      - Dolby AC-3 / Dolby Digital
     546                                'ac3'  => array(
     547                                                        'pattern'   => '^\x0B\x77',
     548                                                        'group'     => 'audio',
     549                                                        'module'    => 'ac3',
     550                                                        'mime_type' => 'audio/ac3',
     551                                                ),
     552
     553                                // AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
     554                                'adif' => array(
     555                                                        'pattern'   => '^ADIF',
     556                                                        'group'     => 'audio',
     557                                                        'module'    => 'aac',
     558                                                        'mime_type' => 'application/octet-stream',
     559                                                        'fail_ape'  => 'WARNING',
     560                                                ),
     561
     562/*
     563                                // AA   - audio       - Audible Audiobook
     564                                'aa'   => array(
     565                                                        'pattern'   => '^.{4}\x57\x90\x75\x36',
     566                                                        'group'     => 'audio',
     567                                                        'module'    => 'aa',
     568                                                        'mime_type' => 'audio/audible',
     569                                                ),
     570*/
     571                                // AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
     572                                'adts' => array(
     573                                                        'pattern'   => '^\xFF[\xF0-\xF1\xF8-\xF9]',
     574                                                        'group'     => 'audio',
     575                                                        'module'    => 'aac',
     576                                                        'mime_type' => 'application/octet-stream',
     577                                                        'fail_ape'  => 'WARNING',
     578                                                ),
     579
     580
     581                                // AU   - audio       - NeXT/Sun AUdio (AU)
     582                                'au'   => array(
     583                                                        'pattern'   => '^\.snd',
     584                                                        'group'     => 'audio',
     585                                                        'module'    => 'au',
     586                                                        'mime_type' => 'audio/basic',
     587                                                ),
     588
     589                                // AVR  - audio       - Audio Visual Research
     590                                'avr'  => array(
     591                                                        'pattern'   => '^2BIT',
     592                                                        'group'     => 'audio',
     593                                                        'module'    => 'avr',
     594                                                        'mime_type' => 'application/octet-stream',
     595                                                ),
     596
     597                                // BONK - audio       - Bonk v0.9+
     598                                'bonk' => array(
     599                                                        'pattern'   => '^\x00(BONK|INFO|META| ID3)',
     600                                                        'group'     => 'audio',
     601                                                        'module'    => 'bonk',
     602                                                        'mime_type' => 'audio/xmms-bonk',
     603                                                ),
     604
     605                                // DSS  - audio       - Digital Speech Standard
     606                                'dss'  => array(
     607                                                        'pattern'   => '^[\x02-\x03]ds[s2]',
     608                                                        'group'     => 'audio',
     609                                                        'module'    => 'dss',
     610                                                        'mime_type' => 'application/octet-stream',
     611                                                ),
     612
     613                                // DTS  - audio       - Dolby Theatre System
     614                                'dts'  => array(
     615                                                        'pattern'   => '^\x7F\xFE\x80\x01',
     616                                                        'group'     => 'audio',
     617                                                        'module'    => 'dts',
     618                                                        'mime_type' => 'audio/dts',
     619                                                ),
     620
     621                                // FLAC - audio       - Free Lossless Audio Codec
     622                                'flac' => array(
     623                                                        'pattern'   => '^fLaC',
     624                                                        'group'     => 'audio',
     625                                                        'module'    => 'flac',
     626                                                        'mime_type' => 'audio/x-flac',
     627                                                ),
     628
     629                                // LA   - audio       - Lossless Audio (LA)
     630                                'la'   => array(
     631                                                        'pattern'   => '^LA0[2-4]',
     632                                                        'group'     => 'audio',
     633                                                        'module'    => 'la',
     634                                                        'mime_type' => 'application/octet-stream',
     635                                                ),
     636
     637                                // LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
     638                                'lpac' => array(
     639                                                        'pattern'   => '^LPAC',
     640                                                        'group'     => 'audio',
     641                                                        'module'    => 'lpac',
     642                                                        'mime_type' => 'application/octet-stream',
     643                                                ),
     644
     645                                // MIDI - audio       - MIDI (Musical Instrument Digital Interface)
     646                                'midi' => array(
     647                                                        'pattern'   => '^MThd',
     648                                                        'group'     => 'audio',
     649                                                        'module'    => 'midi',
     650                                                        'mime_type' => 'audio/midi',
     651                                                ),
     652
     653                                // MAC  - audio       - Monkey's Audio Compressor
     654                                'mac'  => array(
     655                                                        'pattern'   => '^MAC ',
     656                                                        'group'     => 'audio',
     657                                                        'module'    => 'monkey',
     658                                                        'mime_type' => 'application/octet-stream',
     659                                                ),
     660
     661// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
     662//                              // MOD  - audio       - MODule (assorted sub-formats)
     663//                              'mod'  => array(
     664//                                                      'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
     665//                                                      'group'     => 'audio',
     666//                                                      'module'    => 'mod',
     667//                                                      'option'    => 'mod',
     668//                                                      'mime_type' => 'audio/mod',
     669//                                              ),
     670
     671                                // MOD  - audio       - MODule (Impulse Tracker)
     672                                'it'   => array(
     673                                                        'pattern'   => '^IMPM',
     674                                                        'group'     => 'audio',
     675                                                        'module'    => 'mod',
     676                                                        //'option'    => 'it',
     677                                                        'mime_type' => 'audio/it',
     678                                                ),
     679
     680                                // MOD  - audio       - MODule (eXtended Module, various sub-formats)
     681                                'xm'   => array(
     682                                                        'pattern'   => '^Extended Module',
     683                                                        'group'     => 'audio',
     684                                                        'module'    => 'mod',
     685                                                        //'option'    => 'xm',
     686                                                        'mime_type' => 'audio/xm',
     687                                                ),
     688
     689                                // MOD  - audio       - MODule (ScreamTracker)
     690                                's3m'  => array(
     691                                                        'pattern'   => '^.{44}SCRM',
     692                                                        'group'     => 'audio',
     693                                                        'module'    => 'mod',
     694                                                        //'option'    => 's3m',
     695                                                        'mime_type' => 'audio/s3m',
     696                                                ),
     697
     698                                // MPC  - audio       - Musepack / MPEGplus
     699                                'mpc'  => array(
     700                                                        'pattern'   => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])',
     701                                                        'group'     => 'audio',
     702                                                        'module'    => 'mpc',
     703                                                        'mime_type' => 'audio/x-musepack',
     704                                                ),
     705
     706                                // MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
     707                                'mp3'  => array(
     708                                                        'pattern'   => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]',
     709                                                        'group'     => 'audio',
     710                                                        'module'    => 'mp3',
     711                                                        'mime_type' => 'audio/mpeg',
     712                                                ),
     713
     714                                // OFR  - audio       - OptimFROG
     715                                'ofr'  => array(
     716                                                        'pattern'   => '^(\*RIFF|OFR)',
     717                                                        'group'     => 'audio',
     718                                                        'module'    => 'optimfrog',
     719                                                        'mime_type' => 'application/octet-stream',
     720                                                ),
     721
     722                                // RKAU - audio       - RKive AUdio compressor
     723                                'rkau' => array(
     724                                                        'pattern'   => '^RKA',
     725                                                        'group'     => 'audio',
     726                                                        'module'    => 'rkau',
     727                                                        'mime_type' => 'application/octet-stream',
     728                                                ),
     729
     730                                // SHN  - audio       - Shorten
     731                                'shn'  => array(
     732                                                        'pattern'   => '^ajkg',
     733                                                        'group'     => 'audio',
     734                                                        'module'    => 'shorten',
     735                                                        'mime_type' => 'audio/xmms-shn',
     736                                                        'fail_id3'  => 'ERROR',
     737                                                        'fail_ape'  => 'ERROR',
     738                                                ),
     739
     740                                // TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
     741                                'tta'  => array(
     742                                                        'pattern'   => '^TTA',  // could also be '^TTA(\x01|\x02|\x03|2|1)'
     743                                                        'group'     => 'audio',
     744                                                        'module'    => 'tta',
     745                                                        'mime_type' => 'application/octet-stream',
     746                                                ),
     747
     748                                // VOC  - audio       - Creative Voice (VOC)
     749                                'voc'  => array(
     750                                                        'pattern'   => '^Creative Voice File',
     751                                                        'group'     => 'audio',
     752                                                        'module'    => 'voc',
     753                                                        'mime_type' => 'audio/voc',
     754                                                ),
     755
     756                                // VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
     757                                'vqf'  => array(
     758                                                        'pattern'   => '^TWIN',
     759                                                        'group'     => 'audio',
     760                                                        'module'    => 'vqf',
     761                                                        'mime_type' => 'application/octet-stream',
     762                                                ),
     763
     764                                // WV  - audio        - WavPack (v4.0+)
     765                                'wv'   => array(
     766                                                        'pattern'   => '^wvpk',
     767                                                        'group'     => 'audio',
     768                                                        'module'    => 'wavpack',
     769                                                        'mime_type' => 'application/octet-stream',
     770                                                ),
     771
     772
     773                                // Audio-Video formats
     774
     775                                // ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
     776                                'asf'  => array(
     777                                                        'pattern'   => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
     778                                                        'group'     => 'audio-video',
     779                                                        'module'    => 'asf',
     780                                                        'mime_type' => 'video/x-ms-asf',
     781                                                        'iconv_req' => false,
     782                                                ),
     783
     784                                // BINK - audio/video - Bink / Smacker
     785                                'bink' => array(
     786                                                        'pattern'   => '^(BIK|SMK)',
     787                                                        'group'     => 'audio-video',
     788                                                        'module'    => 'bink',
     789                                                        'mime_type' => 'application/octet-stream',
     790                                                ),
     791
     792                                // FLV  - audio/video - FLash Video
     793                                'flv' => array(
     794                                                        'pattern'   => '^FLV\x01',
     795                                                        'group'     => 'audio-video',
     796                                                        'module'    => 'flv',
     797                                                        'mime_type' => 'video/x-flv',
     798                                                ),
     799
     800                                // MKAV - audio/video - Mastroka
     801                                'matroska' => array(
     802                                                        'pattern'   => '^\x1A\x45\xDF\xA3',
     803                                                        'group'     => 'audio-video',
     804                                                        'module'    => 'matroska',
     805                                                        'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
     806                                                ),
     807
     808                                // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
     809                                'mpeg' => array(
     810                                                        'pattern'   => '^\x00\x00\x01(\xBA|\xB3)',
     811                                                        'group'     => 'audio-video',
     812                                                        'module'    => 'mpeg',
     813                                                        'mime_type' => 'video/mpeg',
     814                                                ),
     815
     816                                // NSV  - audio/video - Nullsoft Streaming Video (NSV)
     817                                'nsv'  => array(
     818                                                        'pattern'   => '^NSV[sf]',
     819                                                        'group'     => 'audio-video',
     820                                                        'module'    => 'nsv',
     821                                                        'mime_type' => 'application/octet-stream',
     822                                                ),
     823
     824                                // Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
     825                                'ogg'  => array(
     826                                                        'pattern'   => '^OggS',
     827                                                        'group'     => 'audio',
     828                                                        'module'    => 'ogg',
     829                                                        'mime_type' => 'application/ogg',
     830                                                        'fail_id3'  => 'WARNING',
     831                                                        'fail_ape'  => 'WARNING',
     832                                                ),
     833
     834                                // QT   - audio/video - Quicktime
     835                                'quicktime' => array(
     836                                                        'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
     837                                                        'group'     => 'audio-video',
     838                                                        'module'    => 'quicktime',
     839                                                        'mime_type' => 'video/quicktime',
     840                                                ),
     841
     842                                // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF)
     843                                'riff' => array(
     844                                                        'pattern'   => '^(RIFF|SDSS|FORM)',
     845                                                        'group'     => 'audio-video',
     846                                                        'module'    => 'riff',
     847                                                        'mime_type' => 'audio/x-wave',
     848                                                        'fail_ape'  => 'WARNING',
     849                                                ),
     850
     851                                // Real - audio/video - RealAudio, RealVideo
     852                                'real' => array(
     853                                                        'pattern'   => '^(\\.RMF|\\.ra)',
     854                                                        'group'     => 'audio-video',
     855                                                        'module'    => 'real',
     856                                                        'mime_type' => 'audio/x-realaudio',
     857                                                ),
     858
     859                                // SWF - audio/video - ShockWave Flash
     860                                'swf' => array(
     861                                                        'pattern'   => '^(F|C)WS',
     862                                                        'group'     => 'audio-video',
     863                                                        'module'    => 'swf',
     864                                                        'mime_type' => 'application/x-shockwave-flash',
     865                                                ),
     866
     867                                // TS - audio/video - MPEG-2 Transport Stream
     868                                'ts' => array(
     869                                                        'pattern'   => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
     870                                                        'group'     => 'audio-video',
     871                                                        'module'    => 'ts',
     872                                                        'mime_type' => 'video/MP2T',
     873                                                ),
     874
     875
     876                                // Still-Image formats
     877
     878                                // BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
     879                                'bmp'  => array(
     880                                                        'pattern'   => '^BM',
     881                                                        'group'     => 'graphic',
     882                                                        'module'    => 'bmp',
     883                                                        'mime_type' => 'image/bmp',
     884                                                        'fail_id3'  => 'ERROR',
     885                                                        'fail_ape'  => 'ERROR',
     886                                                ),
     887
     888                                // GIF  - still image - Graphics Interchange Format
     889                                'gif'  => array(
     890                                                        'pattern'   => '^GIF',
     891                                                        'group'     => 'graphic',
     892                                                        'module'    => 'gif',
     893                                                        'mime_type' => 'image/gif',
     894                                                        'fail_id3'  => 'ERROR',
     895                                                        'fail_ape'  => 'ERROR',
     896                                                ),
     897
     898                                // JPEG - still image - Joint Photographic Experts Group (JPEG)
     899                                'jpg'  => array(
     900                                                        'pattern'   => '^\xFF\xD8\xFF',
     901                                                        'group'     => 'graphic',
     902                                                        'module'    => 'jpg',
     903                                                        'mime_type' => 'image/jpeg',
     904                                                        'fail_id3'  => 'ERROR',
     905                                                        'fail_ape'  => 'ERROR',
     906                                                ),
     907
     908                                // PCD  - still image - Kodak Photo CD
     909                                'pcd'  => array(
     910                                                        'pattern'   => '^.{2048}PCD_IPI\x00',
     911                                                        'group'     => 'graphic',
     912                                                        'module'    => 'pcd',
     913                                                        'mime_type' => 'image/x-photo-cd',
     914                                                        'fail_id3'  => 'ERROR',
     915                                                        'fail_ape'  => 'ERROR',
     916                                                ),
     917
     918
     919                                // PNG  - still image - Portable Network Graphics (PNG)
     920                                'png'  => array(
     921                                                        'pattern'   => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
     922                                                        'group'     => 'graphic',
     923                                                        'module'    => 'png',
     924                                                        'mime_type' => 'image/png',
     925                                                        'fail_id3'  => 'ERROR',
     926                                                        'fail_ape'  => 'ERROR',
     927                                                ),
     928
     929
     930                                // SVG  - still image - Scalable Vector Graphics (SVG)
     931                                'svg'  => array(
     932                                                        'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
     933                                                        'group'     => 'graphic',
     934                                                        'module'    => 'svg',
     935                                                        'mime_type' => 'image/svg+xml',
     936                                                        'fail_id3'  => 'ERROR',
     937                                                        'fail_ape'  => 'ERROR',
     938                                                ),
     939
     940
     941                                // TIFF - still image - Tagged Information File Format (TIFF)
     942                                'tiff' => array(
     943                                                        'pattern'   => '^(II\x2A\x00|MM\x00\x2A)',
     944                                                        'group'     => 'graphic',
     945                                                        'module'    => 'tiff',
     946                                                        'mime_type' => 'image/tiff',
     947                                                        'fail_id3'  => 'ERROR',
     948                                                        'fail_ape'  => 'ERROR',
     949                                                ),
     950
     951
     952                                // EFAX - still image - eFax (TIFF derivative)
     953                                'efax'  => array(
     954                                                        'pattern'   => '^\xDC\xFE',
     955                                                        'group'     => 'graphic',
     956                                                        'module'    => 'efax',
     957                                                        'mime_type' => 'image/efax',
     958                                                        'fail_id3'  => 'ERROR',
     959                                                        'fail_ape'  => 'ERROR',
     960                                                ),
     961
     962
     963                                // Data formats
     964
     965                                // ISO  - data        - International Standards Organization (ISO) CD-ROM Image
     966                                'iso'  => array(
     967                                                        'pattern'   => '^.{32769}CD001',
     968                                                        'group'     => 'misc',
     969                                                        'module'    => 'iso',
     970                                                        'mime_type' => 'application/octet-stream',
     971                                                        'fail_id3'  => 'ERROR',
     972                                                        'fail_ape'  => 'ERROR',
     973                                                        'iconv_req' => false,
     974                                                ),
     975
     976                                // RAR  - data        - RAR compressed data
     977                                'rar'  => array(
     978                                                        'pattern'   => '^Rar\!',
     979                                                        'group'     => 'archive',
     980                                                        'module'    => 'rar',
     981                                                        'mime_type' => 'application/octet-stream',
     982                                                        'fail_id3'  => 'ERROR',
     983                                                        'fail_ape'  => 'ERROR',
     984                                                ),
     985
     986                                // SZIP - audio/data  - SZIP compressed data
     987                                'szip' => array(
     988                                                        'pattern'   => '^SZ\x0A\x04',
     989                                                        'group'     => 'archive',
     990                                                        'module'    => 'szip',
     991                                                        'mime_type' => 'application/octet-stream',
     992                                                        'fail_id3'  => 'ERROR',
     993                                                        'fail_ape'  => 'ERROR',
     994                                                ),
     995
     996                                // TAR  - data        - TAR compressed data
     997                                'tar'  => array(
     998                                                        'pattern'   => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}',
     999                                                        'group'     => 'archive',
     1000                                                        'module'    => 'tar',
     1001                                                        'mime_type' => 'application/x-tar',
     1002                                                        'fail_id3'  => 'ERROR',
     1003                                                        'fail_ape'  => 'ERROR',
     1004                                                ),
     1005
     1006                                // GZIP  - data        - GZIP compressed data
     1007                                'gz'  => array(
     1008                                                        'pattern'   => '^\x1F\x8B\x08',
     1009                                                        'group'     => 'archive',
     1010                                                        'module'    => 'gzip',
     1011                                                        'mime_type' => 'application/x-gzip',
     1012                                                        'fail_id3'  => 'ERROR',
     1013                                                        'fail_ape'  => 'ERROR',
     1014                                                ),
     1015
     1016                                // ZIP  - data         - ZIP compressed data
     1017                                'zip'  => array(
     1018                                                        'pattern'   => '^PK\x03\x04',
     1019                                                        'group'     => 'archive',
     1020                                                        'module'    => 'zip',
     1021                                                        'mime_type' => 'application/zip',
     1022                                                        'fail_id3'  => 'ERROR',
     1023                                                        'fail_ape'  => 'ERROR',
     1024                                                ),
     1025
     1026
     1027                                // Misc other formats
     1028
     1029                                // PAR2 - data        - Parity Volume Set Specification 2.0
     1030                                'par2' => array (
     1031                                                        'pattern'   => '^PAR2\x00PKT',
     1032                                                        'group'     => 'misc',
     1033                                                        'module'    => 'par2',
     1034                                                        'mime_type' => 'application/octet-stream',
     1035                                                        'fail_id3'  => 'ERROR',
     1036                                                        'fail_ape'  => 'ERROR',
     1037                                                ),
     1038
     1039                                // PDF  - data        - Portable Document Format
     1040                                'pdf'  => array(
     1041                                                        'pattern'   => '^\x25PDF',
     1042                                                        'group'     => 'misc',
     1043                                                        'module'    => 'pdf',
     1044                                                        'mime_type' => 'application/pdf',
     1045                                                        'fail_id3'  => 'ERROR',
     1046                                                        'fail_ape'  => 'ERROR',
     1047                                                ),
     1048
     1049                                // MSOFFICE  - data   - ZIP compressed data
     1050                                'msoffice' => array(
     1051                                                        'pattern'   => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
     1052                                                        'group'     => 'misc',
     1053                                                        'module'    => 'msoffice',
     1054                                                        'mime_type' => 'application/octet-stream',
     1055                                                        'fail_id3'  => 'ERROR',
     1056                                                        'fail_ape'  => 'ERROR',
     1057                                                ),
     1058
     1059                                 // CUE  - data       - CUEsheet (index to single-file disc images)
     1060                                 'cue' => array(
     1061                                                        'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
     1062                                                        'group'     => 'misc',
     1063                                                        'module'    => 'cue',
     1064                                                        'mime_type' => 'application/octet-stream',
     1065                                                   ),
     1066
     1067                        );
     1068                }
     1069
     1070                return $format_info;
     1071        }
     1072
     1073
     1074
     1075        public function GetFileFormat(&$filedata, $filename='') {
     1076                // this function will determine the format of a file based on usually
     1077                // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
     1078                // and in the case of ISO CD image, 6 bytes offset 32kb from the start
     1079                // of the file).
     1080
     1081                // Identify file format - loop through $format_info and detect with reg expr
     1082                foreach ($this->GetFileFormatArray() as $format_name => $info) {
     1083                        // The /s switch on preg_match() forces preg_match() NOT to treat
     1084                        // newline (0x0A) characters as special chars but do a binary match
     1085                        if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
     1086                                $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
     1087                                return $info;
     1088                        }
     1089                }
     1090
     1091
     1092                if (preg_match('#\.mp[123a]$#i', $filename)) {
     1093                        // Too many mp3 encoders on the market put gabage in front of mpeg files
     1094                        // use assume format on these if format detection failed
     1095                        $GetFileFormatArray = $this->GetFileFormatArray();
     1096                        $info = $GetFileFormatArray['mp3'];
     1097                        $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
     1098                        return $info;
     1099                } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
     1100                        // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
     1101                        // so until I think of something better, just go by filename if all other format checks fail
     1102                        // and verify there's at least one instance of "TRACK xx AUDIO" in the file
     1103                        $GetFileFormatArray = $this->GetFileFormatArray();
     1104                        $info = $GetFileFormatArray['cue'];
     1105                        $info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
     1106                        return $info;
     1107                }
     1108
     1109                return false;
     1110        }
     1111
     1112
     1113        // converts array to $encoding charset from $this->encoding
     1114        public function CharConvert(&$array, $encoding) {
     1115
     1116                // identical encoding - end here
     1117                if ($encoding == $this->encoding) {
     1118                        return;
     1119                }
     1120
     1121                // loop thru array
     1122                foreach ($array as $key => $value) {
     1123
     1124                        // go recursive
     1125                        if (is_array($value)) {
     1126                                $this->CharConvert($array[$key], $encoding);
     1127                        }
     1128
     1129                        // convert string
     1130                        elseif (is_string($value)) {
     1131                                $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
     1132                        }
     1133                }
     1134        }
     1135
     1136
     1137        public function HandleAllTags() {
     1138
     1139                // key name => array (tag name, character encoding)
     1140                static $tags;
     1141                if (empty($tags)) {
     1142                        $tags = array(
     1143                                'asf'       => array('asf'           , 'UTF-16LE'),
     1144                                'midi'      => array('midi'          , 'ISO-8859-1'),
     1145                                'nsv'       => array('nsv'           , 'ISO-8859-1'),
     1146                                'ogg'       => array('vorbiscomment' , 'UTF-8'),
     1147                                'png'       => array('png'           , 'UTF-8'),
     1148                                'tiff'      => array('tiff'          , 'ISO-8859-1'),
     1149                                'quicktime' => array('quicktime'     , 'UTF-8'),
     1150                                'real'      => array('real'          , 'ISO-8859-1'),
     1151                                'vqf'       => array('vqf'           , 'ISO-8859-1'),
     1152                                'zip'       => array('zip'           , 'ISO-8859-1'),
     1153                                'riff'      => array('riff'          , 'ISO-8859-1'),
     1154                                'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
     1155                                'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
     1156                                'id3v2'     => array('id3v2'         , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8
     1157                                'ape'       => array('ape'           , 'UTF-8'),
     1158                                'cue'       => array('cue'           , 'ISO-8859-1'),
     1159                                'matroska'  => array('matroska'      , 'UTF-8'),
     1160                                'flac'      => array('vorbiscomment' , 'UTF-8'),
     1161                                'divxtag'   => array('divx'          , 'ISO-8859-1'),
     1162                        );
     1163                }
     1164
     1165                // loop through comments array
     1166                foreach ($tags as $comment_name => $tagname_encoding_array) {
     1167                        list($tag_name, $encoding) = $tagname_encoding_array;
     1168
     1169                        // fill in default encoding type if not already present
     1170                        if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
     1171                                $this->info[$comment_name]['encoding'] = $encoding;
     1172                        }
     1173
     1174                        // copy comments if key name set
     1175                        if (!empty($this->info[$comment_name]['comments'])) {
     1176                                foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
     1177                                        foreach ($valuearray as $key => $value) {
     1178                                                if (is_string($value)) {
     1179                                                        $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
     1180                                                }
     1181                                                if ($value) {
     1182                                                        $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value;
     1183                                                }
     1184                                        }
     1185                                        if ($tag_key == 'picture') {
     1186                                                unset($this->info[$comment_name]['comments'][$tag_key]);
     1187                                        }
     1188                                }
     1189
     1190                                if (!isset($this->info['tags'][$tag_name])) {
     1191                                        // comments are set but contain nothing but empty strings, so skip
     1192                                        continue;
     1193                                }
     1194
     1195                                if ($this->option_tags_html) {
     1196                                        foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
     1197                                                foreach ($valuearray as $key => $value) {
     1198                                                        if (is_string($value)) {
     1199                                                                //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding);
     1200                                                                $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('&#0;', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding)));
     1201                                                        } else {
     1202                                                                $this->info['tags_html'][$tag_name][$tag_key][$key] = $value;
     1203                                                        }
     1204                                                }
     1205                                        }
     1206                                }
     1207
     1208                                $this->CharConvert($this->info['tags'][$tag_name], $encoding);           // only copy gets converted!
     1209                        }
     1210
     1211                }
     1212
     1213                // pictures can take up a lot of space, and we don't need multiple copies of them
     1214                // let there be a single copy in [comments][picture], and not elsewhere
     1215                if (!empty($this->info['tags'])) {
     1216                        $unset_keys = array('tags', 'tags_html');
     1217                        foreach ($this->info['tags'] as $tagtype => $tagarray) {
     1218                                foreach ($tagarray as $tagname => $tagdata) {
     1219                                        if ($tagname == 'picture') {
     1220                                                foreach ($tagdata as $key => $tagarray) {
     1221                                                        $this->info['comments']['picture'][] = $tagarray;
     1222                                                        if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
     1223                                                                if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
     1224                                                                        unset($this->info['tags'][$tagtype][$tagname][$key]);
     1225                                                                }
     1226                                                                if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
     1227                                                                        unset($this->info['tags_html'][$tagtype][$tagname][$key]);
     1228                                                                }
     1229                                                        }
     1230                                                }
     1231                                        }
     1232                                }
     1233                                foreach ($unset_keys as $unset_key) {
     1234                                        // remove possible empty keys from (e.g. [tags][id3v2][picture])
     1235                                        if (empty($this->info[$unset_key][$tagtype]['picture'])) {
     1236                                                unset($this->info[$unset_key][$tagtype]['picture']);
     1237                                        }
     1238                                        if (empty($this->info[$unset_key][$tagtype])) {
     1239                                                unset($this->info[$unset_key][$tagtype]);
     1240                                        }
     1241                                        if (empty($this->info[$unset_key])) {
     1242                                                unset($this->info[$unset_key]);
     1243                                        }
     1244                                }
     1245                                // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
     1246                                if (isset($this->info[$tagtype]['comments']['picture'])) {
     1247                                        unset($this->info[$tagtype]['comments']['picture']);
     1248                                }
     1249                                if (empty($this->info[$tagtype]['comments'])) {
     1250                                        unset($this->info[$tagtype]['comments']);
     1251                                }
     1252                                if (empty($this->info[$tagtype])) {
     1253                                        unset($this->info[$tagtype]);
     1254                                }
     1255                        }
     1256                }
     1257                return true;
     1258        }
     1259
     1260
     1261        public function getHashdata($algorithm) {
     1262                switch ($algorithm) {
     1263                        case 'md5':
     1264                        case 'sha1':
     1265                                break;
     1266
     1267                        default:
     1268                                return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
     1269                                break;
     1270                }
     1271
     1272                if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
     1273
     1274                        // We cannot get an identical md5_data value for Ogg files where the comments
     1275                        // span more than 1 Ogg page (compared to the same audio data with smaller
     1276                        // comments) using the normal getID3() method of MD5'ing the data between the
     1277                        // end of the comments and the end of the file (minus any trailing tags),
     1278                        // because the page sequence numbers of the pages that the audio data is on
     1279                        // do not match. Under normal circumstances, where comments are smaller than
     1280                        // the nominal 4-8kB page size, then this is not a problem, but if there are
     1281                        // very large comments, the only way around it is to strip off the comment
     1282                        // tags with vorbiscomment and MD5 that file.
     1283                        // This procedure must be applied to ALL Ogg files, not just the ones with
     1284                        // comments larger than 1 page, because the below method simply MD5's the
     1285                        // whole file with the comments stripped, not just the portion after the
     1286                        // comments block (which is the standard getID3() method.
     1287
     1288                        // The above-mentioned problem of comments spanning multiple pages and changing
     1289                        // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
     1290                        // currently vorbiscomment only works on OggVorbis files.
     1291
     1292                        if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
     1293
     1294                                $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
     1295                                $this->info[$algorithm.'_data'] = false;
     1296
     1297                        } else {
     1298
     1299                                // Prevent user from aborting script
     1300                                $old_abort = ignore_user_abort(true);
     1301
     1302                                // Create empty file
     1303                                $empty = tempnam(GETID3_TEMP_DIR, 'getID3');
     1304                                touch($empty);
     1305
     1306                                // Use vorbiscomment to make temp file without comments
     1307                                $temp = tempnam(GETID3_TEMP_DIR, 'getID3');
     1308                                $file = $this->info['filenamepath'];
     1309
     1310                                if (GETID3_OS_ISWINDOWS) {
     1311
     1312                                        if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
     1313
     1314                                                $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
     1315                                                $VorbisCommentError = `$commandline`;
     1316
     1317                                        } else {
     1318
     1319                                                $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
     1320
     1321                                        }
     1322
     1323                                } else {
     1324
     1325                                        $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1';
     1326                                        $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
     1327                                        $VorbisCommentError = `$commandline`;
     1328
     1329                                }
     1330
     1331                                if (!empty($VorbisCommentError)) {
     1332
     1333                                        $this->info['warning'][]         = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError;
     1334                                        $this->info[$algorithm.'_data']  = false;
     1335
     1336                                } else {
     1337
     1338                                        // Get hash of newly created file
     1339                                        switch ($algorithm) {
     1340                                                case 'md5':
     1341                                                        $this->info[$algorithm.'_data'] = md5_file($temp);
     1342                                                        break;
     1343
     1344                                                case 'sha1':
     1345                                                        $this->info[$algorithm.'_data'] = sha1_file($temp);
     1346                                                        break;
     1347                                        }
     1348                                }
     1349
     1350                                // Clean up
     1351                                unlink($empty);
     1352                                unlink($temp);
     1353
     1354                                // Reset abort setting
     1355                                ignore_user_abort($old_abort);
     1356
     1357                        }
     1358
     1359                } else {
     1360
     1361                        if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
     1362
     1363                                // get hash from part of file
     1364                                $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
     1365
     1366                        } else {
     1367
     1368                                // get hash from whole file
     1369                                switch ($algorithm) {
     1370                                        case 'md5':
     1371                                                $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
     1372                                                break;
     1373
     1374                                        case 'sha1':
     1375                                                $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
     1376                                                break;
     1377                                }
     1378                        }
     1379
     1380                }
     1381                return true;
     1382        }
     1383
     1384
     1385        public function ChannelsBitratePlaytimeCalculations() {
     1386
     1387                // set channelmode on audio
     1388                if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
     1389                        // ignore
     1390                } elseif ($this->info['audio']['channels'] == 1) {
     1391                        $this->info['audio']['channelmode'] = 'mono';
     1392                } elseif ($this->info['audio']['channels'] == 2) {
     1393                        $this->info['audio']['channelmode'] = 'stereo';
     1394                }
     1395
     1396                // Calculate combined bitrate - audio + video
     1397                $CombinedBitrate  = 0;
     1398                $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
     1399                $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
     1400                if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
     1401                        $this->info['bitrate'] = $CombinedBitrate;
     1402                }
     1403                //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
     1404                //      // for example, VBR MPEG video files cannot determine video bitrate:
     1405                //      // should not set overall bitrate and playtime from audio bitrate only
     1406                //      unset($this->info['bitrate']);
     1407                //}
     1408
     1409                // video bitrate undetermined, but calculable
     1410                if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
     1411                        // if video bitrate not set
     1412                        if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
     1413                                // AND if audio bitrate is set to same as overall bitrate
     1414                                if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
     1415                                        // AND if playtime is set
     1416                                        if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
     1417                                                // AND if AV data offset start/end is known
     1418                                                // THEN we can calculate the video bitrate
     1419                                                $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
     1420                                                $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
     1421                                        }
     1422                                }
     1423                        }
     1424                }
     1425
     1426                if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
     1427                        $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
     1428                }
     1429
     1430                if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
     1431                        $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
     1432                }
     1433                if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
     1434                        if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
     1435                                // audio only
     1436                                $this->info['audio']['bitrate'] = $this->info['bitrate'];
     1437                        } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
     1438                                // video only
     1439                                $this->info['video']['bitrate'] = $this->info['bitrate'];
     1440                        }
     1441                }
     1442
     1443                // Set playtime string
     1444                if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
     1445                        $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
     1446                }
     1447        }
     1448
     1449
     1450        public function CalculateCompressionRatioVideo() {
     1451                if (empty($this->info['video'])) {
     1452                        return false;
     1453                }
     1454                if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
     1455                        return false;
     1456                }
     1457                if (empty($this->info['video']['bits_per_sample'])) {
     1458                        return false;
     1459                }
     1460
     1461                switch ($this->info['video']['dataformat']) {
     1462                        case 'bmp':
     1463                        case 'gif':
     1464                        case 'jpeg':
     1465                        case 'jpg':
     1466                        case 'png':
     1467                        case 'tiff':
     1468                                $FrameRate = 1;
     1469                                $PlaytimeSeconds = 1;
     1470                                $BitrateCompressed = $this->info['filesize'] * 8;
     1471                                break;
     1472
     1473                        default:
     1474                                if (!empty($this->info['video']['frame_rate'])) {
     1475                                        $FrameRate = $this->info['video']['frame_rate'];
     1476                                } else {
     1477                                        return false;
     1478                                }
     1479                                if (!empty($this->info['playtime_seconds'])) {
     1480                                        $PlaytimeSeconds = $this->info['playtime_seconds'];
     1481                                } else {
     1482                                        return false;
     1483                                }
     1484                                if (!empty($this->info['video']['bitrate'])) {
     1485                                        $BitrateCompressed = $this->info['video']['bitrate'];
     1486                                } else {
     1487                                        return false;
     1488                                }
     1489                                break;
     1490                }
     1491                $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
     1492
     1493                $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
     1494                return true;
     1495        }
     1496
     1497
     1498        public function CalculateCompressionRatioAudio() {
     1499                if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
     1500                        return false;
     1501                }
     1502                $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16));
     1503
     1504                if (!empty($this->info['audio']['streams'])) {
     1505                        foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
     1506                                if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
     1507                                        $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
     1508                                }
     1509                        }
     1510                }
     1511                return true;
     1512        }
     1513
     1514
     1515        public function CalculateReplayGain() {
     1516                if (isset($this->info['replay_gain'])) {
     1517                        if (!isset($this->info['replay_gain']['reference_volume'])) {
     1518                                $this->info['replay_gain']['reference_volume'] = (double) 89.0;
     1519                        }
     1520                        if (isset($this->info['replay_gain']['track']['adjustment'])) {
     1521                                $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
     1522                        }
     1523                        if (isset($this->info['replay_gain']['album']['adjustment'])) {
     1524                                $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
     1525                        }
     1526
     1527                        if (isset($this->info['replay_gain']['track']['peak'])) {
     1528                                $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
     1529                        }
     1530                        if (isset($this->info['replay_gain']['album']['peak'])) {
     1531                                $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
     1532                        }
     1533                }
     1534                return true;
     1535        }
     1536
     1537        public function ProcessAudioStreams() {
     1538                if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
     1539                        if (!isset($this->info['audio']['streams'])) {
     1540                                foreach ($this->info['audio'] as $key => $value) {
     1541                                        if ($key != 'streams') {
     1542                                                $this->info['audio']['streams'][0][$key] = $value;
     1543                                        }
     1544                                }
     1545                        }
     1546                }
     1547                return true;
     1548        }
     1549
     1550        public function getid3_tempnam() {
     1551                return tempnam($this->tempdir, 'gI3');
     1552        }
     1553
     1554        public function include_module($name) {
     1555                //if (!file_exists($this->include_path.'module.'.$name.'.php')) {
     1556                if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
     1557                        throw new getid3_exception('Required module.'.$name.'.php is missing.');
     1558                }
     1559                include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
     1560                return true;
     1561        }
     1562
     1563}
     1564
     1565
     1566abstract class getid3_handler
     1567{
     1568        protected $getid3;                       // pointer
     1569
     1570        protected $data_string_flag     = false; // analyzing filepointer or string
     1571        protected $data_string          = '';    // string to analyze
     1572        protected $data_string_position = 0;     // seek position in string
     1573        protected $data_string_length   = 0;     // string length
     1574
     1575        private $dependency_to = null;
     1576
     1577
     1578        public function __construct(getID3 $getid3, $call_module=null) {
     1579                $this->getid3 = $getid3;
     1580
     1581                if ($call_module) {
     1582                        $this->dependency_to = str_replace('getid3_', '', $call_module);
     1583                }
     1584        }
     1585
     1586
     1587        // Analyze from file pointer
     1588        abstract public function Analyze();
     1589
     1590
     1591        // Analyze from string instead
     1592        public function AnalyzeString($string) {
     1593                // Enter string mode
     1594            $this->setStringMode($string);
     1595
     1596                // Save info
     1597                $saved_avdataoffset = $this->getid3->info['avdataoffset'];
     1598                $saved_avdataend    = $this->getid3->info['avdataend'];
     1599                $saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
     1600
     1601                // Reset some info
     1602                $this->getid3->info['avdataoffset'] = 0;
     1603                $this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
     1604
     1605                // Analyze
     1606                $this->Analyze();
     1607
     1608                // Restore some info
     1609                $this->getid3->info['avdataoffset'] = $saved_avdataoffset;
     1610                $this->getid3->info['avdataend']    = $saved_avdataend;
     1611                $this->getid3->info['filesize']     = $saved_filesize;
     1612
     1613                // Exit string mode
     1614                $this->data_string_flag = false;
     1615        }
     1616
     1617        public function setStringMode($string) {
     1618                $this->data_string_flag   = true;
     1619                $this->data_string        = $string;
     1620                $this->data_string_length = strlen($string);
     1621        }
     1622
     1623        protected function ftell() {
     1624                if ($this->data_string_flag) {
     1625                        return $this->data_string_position;
     1626                }
     1627                return ftell($this->getid3->fp);
     1628        }
     1629
     1630        protected function fread($bytes) {
     1631                if ($this->data_string_flag) {
     1632                        $this->data_string_position += $bytes;
     1633                        return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
     1634                }
     1635            $pos = $this->ftell() + $bytes;
     1636            if (!getid3_lib::intValueSupported($pos)) {
     1637                        throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
     1638            }
     1639                return fread($this->getid3->fp, $bytes);
     1640        }
     1641
     1642        protected function fseek($bytes, $whence=SEEK_SET) {
     1643                if ($this->data_string_flag) {
     1644                        switch ($whence) {
     1645                                case SEEK_SET:
     1646                                        $this->data_string_position = $bytes;
     1647                                        break;
     1648
     1649                                case SEEK_CUR:
     1650                                        $this->data_string_position += $bytes;
     1651                                        break;
     1652
     1653                                case SEEK_END:
     1654                                        $this->data_string_position = $this->data_string_length + $bytes;
     1655                                        break;
     1656                        }
     1657                        return 0;
     1658            } else {
     1659                $pos = $bytes;
     1660                if ($whence == SEEK_CUR) {
     1661                                $pos = $this->ftell() + $bytes;
     1662                } elseif ($whence == SEEK_END) {
     1663                                $pos = $this->info['filesize'] + $bytes;
     1664                }
     1665                if (!getid3_lib::intValueSupported($pos)) {
     1666                                throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
     1667                        }
     1668                }
     1669                return fseek($this->getid3->fp, $bytes, $whence);
     1670        }
     1671
     1672        protected function feof() {
     1673                if ($this->data_string_flag) {
     1674                        return $this->data_string_position >= $this->data_string_length;
     1675                }
     1676                return feof($this->getid3->fp);
     1677        }
     1678
     1679        final protected function isDependencyFor($module) {
     1680                return $this->dependency_to == $module;
     1681        }
     1682
     1683        protected function error($text)
     1684        {
     1685                $this->getid3->info['error'][] = $text;
     1686
     1687                return false;
     1688        }
     1689
     1690        protected function warning($text)
     1691        {
     1692                return $this->getid3->warning($text);
     1693        }
     1694
     1695        protected function notice($text)
     1696        {
     1697                // does nothing for now
     1698        }
     1699
     1700        public function saveAttachment($name, $offset, $length, $image_mime=null) {
     1701                try {
     1702
     1703                        // do not extract at all
     1704                        if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
     1705
     1706                                $attachment = null; // do not set any
     1707
     1708                        // extract to return array
     1709                        } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
     1710
     1711                                $this->fseek($offset);
     1712                                $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
     1713                                if ($attachment === false || strlen($attachment) != $length) {
     1714                                        throw new Exception('failed to read attachment data');
     1715                                }
     1716
     1717                        // assume directory path is given
     1718                        } else {
     1719
     1720                                // set up destination path
     1721                                $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
     1722                                if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory
     1723                                        throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
     1724                                }
     1725                                $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
     1726
     1727                                // create dest file
     1728                                if (($fp_dest = fopen($dest, 'wb')) == false) {
     1729                                        throw new Exception('failed to create file '.$dest);
     1730                                }
     1731
     1732                                // copy data
     1733                                $this->fseek($offset);
     1734                                $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
     1735                                $bytesleft = $length;
     1736                                while ($bytesleft > 0) {
     1737                                        if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
     1738                                                throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
     1739                                        }
     1740                                        $bytesleft -= $byteswritten;
     1741                                }
     1742
     1743                                fclose($fp_dest);
     1744                                $attachment = $dest;
     1745
     1746                        }
     1747
     1748                } catch (Exception $e) {
     1749
     1750                        // close and remove dest file if created
     1751                        if (isset($fp_dest) && is_resource($fp_dest)) {
     1752                                fclose($fp_dest);
     1753                                unlink($dest);
     1754                        }
     1755
     1756                        // do not set any is case of error
     1757                        $attachment = null;
     1758                        $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
     1759
     1760                }
     1761
     1762                // seek to the end of attachment
     1763                $this->fseek($offset + $length);
     1764
     1765                return $attachment;
     1766        }
     1767}
     1768
     1769class getid3_exception extends Exception {
     1770        public $message;
     1771}
     1772 No newline at end of file
  • new file wp-includes/ID3/getid3.lib.php

    diff --git wp-includes/ID3/getid3.lib.php wp-includes/ID3/getid3.lib.php
    new file mode 100644
    index 0000000..f0b9c94
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7//                                                             //
     8// getid3.lib.php - part of getID3()                           //
     9// See readme.txt for more details                             //
     10//                                                            ///
     11/////////////////////////////////////////////////////////////////
     12
     13
     14class getid3_lib
     15{
     16
     17        public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') {
     18                $returnstring = '';
     19                for ($i = 0; $i < strlen($string); $i++) {
     20                        if ($hex) {
     21                                $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT);
     22                        } else {
     23                                $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤');
     24                        }
     25                        if ($spaces) {
     26                                $returnstring .= ' ';
     27                        }
     28                }
     29                if (!empty($htmlencoding)) {
     30                        if ($htmlencoding === true) {
     31                                $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean
     32                        }
     33                        $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding);
     34                }
     35                return $returnstring;
     36        }
     37
     38        public static function trunc($floatnumber) {
     39                // truncates a floating-point number at the decimal point
     40                // returns int (if possible, otherwise float)
     41                if ($floatnumber >= 1) {
     42                        $truncatednumber = floor($floatnumber);
     43                } elseif ($floatnumber <= -1) {
     44                        $truncatednumber = ceil($floatnumber);
     45                } else {
     46                        $truncatednumber = 0;
     47                }
     48                if (self::intValueSupported($truncatednumber)) {
     49                        $truncatednumber = (int) $truncatednumber;
     50                }
     51                return $truncatednumber;
     52        }
     53
     54
     55        public static function safe_inc(&$variable, $increment=1) {
     56                if (isset($variable)) {
     57                        $variable += $increment;
     58                } else {
     59                        $variable = $increment;
     60                }
     61                return true;
     62        }
     63
     64        public static function CastAsInt($floatnum) {
     65                // convert to float if not already
     66                $floatnum = (float) $floatnum;
     67
     68                // convert a float to type int, only if possible
     69                if (self::trunc($floatnum) == $floatnum) {
     70                        // it's not floating point
     71                        if (self::intValueSupported($floatnum)) {
     72                                // it's within int range
     73                                $floatnum = (int) $floatnum;
     74                        }
     75                }
     76                return $floatnum;
     77        }
     78
     79        public static function intValueSupported($num) {
     80                // check if integers are 64-bit
     81                static $hasINT64 = null;
     82                if ($hasINT64 === null) { // 10x faster than is_null()
     83                        $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1
     84                        if (!$hasINT64 && !defined('PHP_INT_MIN')) {
     85                                define('PHP_INT_MIN', ~PHP_INT_MAX);
     86                        }
     87                }
     88                // if integers are 64-bit - no other check required
     89                if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) {
     90                        return true;
     91                }
     92                return false;
     93        }
     94
     95        public static function DecimalizeFraction($fraction) {
     96                list($numerator, $denominator) = explode('/', $fraction);
     97                return $numerator / ($denominator ? $denominator : 1);
     98        }
     99
     100
     101        public static function DecimalBinary2Float($binarynumerator) {
     102                $numerator   = self::Bin2Dec($binarynumerator);
     103                $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator)));
     104                return ($numerator / $denominator);
     105        }
     106
     107
     108        public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
     109                // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
     110                if (strpos($binarypointnumber, '.') === false) {
     111                        $binarypointnumber = '0.'.$binarypointnumber;
     112                } elseif ($binarypointnumber{0} == '.') {
     113                        $binarypointnumber = '0'.$binarypointnumber;
     114                }
     115                $exponent = 0;
     116                while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
     117                        if (substr($binarypointnumber, 1, 1) == '.') {
     118                                $exponent--;
     119                                $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
     120                        } else {
     121                                $pointpos = strpos($binarypointnumber, '.');
     122                                $exponent += ($pointpos - 1);
     123                                $binarypointnumber = str_replace('.', '', $binarypointnumber);
     124                                $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1);
     125                        }
     126                }
     127                $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
     128                return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
     129        }
     130
     131
     132        public static function Float2BinaryDecimal($floatvalue) {
     133                // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
     134                $maxbits = 128; // to how many bits of precision should the calculations be taken?
     135                $intpart   = self::trunc($floatvalue);
     136                $floatpart = abs($floatvalue - $intpart);
     137                $pointbitstring = '';
     138                while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) {
     139                        $floatpart *= 2;
     140                        $pointbitstring .= (string) self::trunc($floatpart);
     141                        $floatpart -= self::trunc($floatpart);
     142                }
     143                $binarypointnumber = decbin($intpart).'.'.$pointbitstring;
     144                return $binarypointnumber;
     145        }
     146
     147
     148        public static function Float2String($floatvalue, $bits) {
     149                // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
     150                switch ($bits) {
     151                        case 32:
     152                                $exponentbits = 8;
     153                                $fractionbits = 23;
     154                                break;
     155
     156                        case 64:
     157                                $exponentbits = 11;
     158                                $fractionbits = 52;
     159                                break;
     160
     161                        default:
     162                                return false;
     163                                break;
     164                }
     165                if ($floatvalue >= 0) {
     166                        $signbit = '0';
     167                } else {
     168                        $signbit = '1';
     169                }
     170                $normalizedbinary  = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits);
     171                $biasedexponent    = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent
     172                $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT);
     173                $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT);
     174
     175                return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
     176        }
     177
     178
     179        public static function LittleEndian2Float($byteword) {
     180                return self::BigEndian2Float(strrev($byteword));
     181        }
     182
     183
     184        public static function BigEndian2Float($byteword) {
     185                // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
     186                // http://www.psc.edu/general/software/packages/ieee/ieee.html
     187                // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
     188
     189                $bitword = self::BigEndian2Bin($byteword);
     190                if (!$bitword) {
     191                        return 0;
     192                }
     193                $signbit = $bitword{0};
     194
     195                switch (strlen($byteword) * 8) {
     196                        case 32:
     197                                $exponentbits = 8;
     198                                $fractionbits = 23;
     199                                break;
     200
     201                        case 64:
     202                                $exponentbits = 11;
     203                                $fractionbits = 52;
     204                                break;
     205
     206                        case 80:
     207                                // 80-bit Apple SANE format
     208                                // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
     209                                $exponentstring = substr($bitword, 1, 15);
     210                                $isnormalized = intval($bitword{16});
     211                                $fractionstring = substr($bitword, 17, 63);
     212                                $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383);
     213                                $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring);
     214                                $floatvalue = $exponent * $fraction;
     215                                if ($signbit == '1') {
     216                                        $floatvalue *= -1;
     217                                }
     218                                return $floatvalue;
     219                                break;
     220
     221                        default:
     222                                return false;
     223                                break;
     224                }
     225                $exponentstring = substr($bitword, 1, $exponentbits);
     226                $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits);
     227                $exponent = self::Bin2Dec($exponentstring);
     228                $fraction = self::Bin2Dec($fractionstring);
     229
     230                if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
     231                        // Not a Number
     232                        $floatvalue = false;
     233                } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
     234                        if ($signbit == '1') {
     235                                $floatvalue = '-infinity';
     236                        } else {
     237                                $floatvalue = '+infinity';
     238                        }
     239                } elseif (($exponent == 0) && ($fraction == 0)) {
     240                        if ($signbit == '1') {
     241                                $floatvalue = -0;
     242                        } else {
     243                                $floatvalue = 0;
     244                        }
     245                        $floatvalue = ($signbit ? 0 : -0);
     246                } elseif (($exponent == 0) && ($fraction != 0)) {
     247                        // These are 'unnormalized' values
     248                        $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring);
     249                        if ($signbit == '1') {
     250                                $floatvalue *= -1;
     251                        }
     252                } elseif ($exponent != 0) {
     253                        $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring));
     254                        if ($signbit == '1') {
     255                                $floatvalue *= -1;
     256                        }
     257                }
     258                return (float) $floatvalue;
     259        }
     260
     261
     262        public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
     263                $intvalue = 0;
     264                $bytewordlen = strlen($byteword);
     265                if ($bytewordlen == 0) {
     266                        return false;
     267                }
     268                for ($i = 0; $i < $bytewordlen; $i++) {
     269                        if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
     270                                //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems
     271                                $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7);
     272                        } else {
     273                                $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
     274                        }
     275                }
     276                if ($signed && !$synchsafe) {
     277                        // synchsafe ints are not allowed to be signed
     278                        if ($bytewordlen <= PHP_INT_SIZE) {
     279                                $signMaskBit = 0x80 << (8 * ($bytewordlen - 1));
     280                                if ($intvalue & $signMaskBit) {
     281                                        $intvalue = 0 - ($intvalue & ($signMaskBit - 1));
     282                                }
     283                        } else {
     284                                throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()');
     285                                break;
     286                        }
     287                }
     288                return self::CastAsInt($intvalue);
     289        }
     290
     291
     292        public static function LittleEndian2Int($byteword, $signed=false) {
     293                return self::BigEndian2Int(strrev($byteword), false, $signed);
     294        }
     295
     296
     297        public static function BigEndian2Bin($byteword) {
     298                $binvalue = '';
     299                $bytewordlen = strlen($byteword);
     300                for ($i = 0; $i < $bytewordlen; $i++) {
     301                        $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
     302                }
     303                return $binvalue;
     304        }
     305
     306
     307        public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
     308                if ($number < 0) {
     309                        throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers');
     310                }
     311                $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF);
     312                $intstring = '';
     313                if ($signed) {
     314                        if ($minbytes > PHP_INT_SIZE) {
     315                                throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()');
     316                        }
     317                        $number = $number & (0x80 << (8 * ($minbytes - 1)));
     318                }
     319                while ($number != 0) {
     320                        $quotient = ($number / ($maskbyte + 1));
     321                        $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring;
     322                        $number = floor($quotient);
     323                }
     324                return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT);
     325        }
     326
     327
     328        public static function Dec2Bin($number) {
     329                while ($number >= 256) {
     330                        $bytes[] = (($number / 256) - (floor($number / 256))) * 256;
     331                        $number = floor($number / 256);
     332                }
     333                $bytes[] = $number;
     334                $binstring = '';
     335                for ($i = 0; $i < count($bytes); $i++) {
     336                        $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
     337                }
     338                return $binstring;
     339        }
     340
     341
     342        public static function Bin2Dec($binstring, $signed=false) {
     343                $signmult = 1;
     344                if ($signed) {
     345                        if ($binstring{0} == '1') {
     346                                $signmult = -1;
     347                        }
     348                        $binstring = substr($binstring, 1);
     349                }
     350                $decvalue = 0;
     351                for ($i = 0; $i < strlen($binstring); $i++) {
     352                        $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
     353                }
     354                return self::CastAsInt($decvalue * $signmult);
     355        }
     356
     357
     358        public static function Bin2String($binstring) {
     359                // return 'hi' for input of '0110100001101001'
     360                $string = '';
     361                $binstringreversed = strrev($binstring);
     362                for ($i = 0; $i < strlen($binstringreversed); $i += 8) {
     363                        $string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string;
     364                }
     365                return $string;
     366        }
     367
     368
     369        public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
     370                $intstring = '';
     371                while ($number > 0) {
     372                        if ($synchsafe) {
     373                                $intstring = $intstring.chr($number & 127);
     374                                $number >>= 7;
     375                        } else {
     376                                $intstring = $intstring.chr($number & 255);
     377                                $number >>= 8;
     378                        }
     379                }
     380                return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
     381        }
     382
     383
     384        public static function array_merge_clobber($array1, $array2) {
     385                // written by kcØhireability*com
     386                // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
     387                if (!is_array($array1) || !is_array($array2)) {
     388                        return false;
     389                }
     390                $newarray = $array1;
     391                foreach ($array2 as $key => $val) {
     392                        if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
     393                                $newarray[$key] = self::array_merge_clobber($newarray[$key], $val);
     394                        } else {
     395                                $newarray[$key] = $val;
     396                        }
     397                }
     398                return $newarray;
     399        }
     400
     401
     402        public static function array_merge_noclobber($array1, $array2) {
     403                if (!is_array($array1) || !is_array($array2)) {
     404                        return false;
     405                }
     406                $newarray = $array1;
     407                foreach ($array2 as $key => $val) {
     408                        if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
     409                                $newarray[$key] = self::array_merge_noclobber($newarray[$key], $val);
     410                        } elseif (!isset($newarray[$key])) {
     411                                $newarray[$key] = $val;
     412                        }
     413                }
     414                return $newarray;
     415        }
     416
     417
     418        public static function ksort_recursive(&$theArray) {
     419                ksort($theArray);
     420                foreach ($theArray as $key => $value) {
     421                        if (is_array($value)) {
     422                                self::ksort_recursive($theArray[$key]);
     423                        }
     424                }
     425                return true;
     426        }
     427
     428        public static function fileextension($filename, $numextensions=1) {
     429                if (strstr($filename, '.')) {
     430                        $reversedfilename = strrev($filename);
     431                        $offset = 0;
     432                        for ($i = 0; $i < $numextensions; $i++) {
     433                                $offset = strpos($reversedfilename, '.', $offset + 1);
     434                                if ($offset === false) {
     435                                        return '';
     436                                }
     437                        }
     438                        return strrev(substr($reversedfilename, 0, $offset));
     439                }
     440                return '';
     441        }
     442
     443
     444        public static function PlaytimeString($seconds) {
     445                $sign = (($seconds < 0) ? '-' : '');
     446                $seconds = round(abs($seconds));
     447                $H = (int) floor( $seconds                            / 3600);
     448                $M = (int) floor(($seconds - (3600 * $H)            ) /   60);
     449                $S = (int) round( $seconds - (3600 * $H) - (60 * $M)        );
     450                return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT);
     451        }
     452
     453
     454        public static function DateMac2Unix($macdate) {
     455                // Macintosh timestamp: seconds since 00:00h January 1, 1904
     456                // UNIX timestamp:      seconds since 00:00h January 1, 1970
     457                return self::CastAsInt($macdate - 2082844800);
     458        }
     459
     460
     461        public static function FixedPoint8_8($rawdata) {
     462                return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
     463        }
     464
     465
     466        public static function FixedPoint16_16($rawdata) {
     467                return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
     468        }
     469
     470
     471        public static function FixedPoint2_30($rawdata) {
     472                $binarystring = self::BigEndian2Bin($rawdata);
     473                return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
     474        }
     475
     476
     477        public static function CreateDeepArray($ArrayPath, $Separator, $Value) {
     478                // assigns $Value to a nested array path:
     479                //   $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt')
     480                // is the same as:
     481                //   $foo = array('path'=>array('to'=>'array('my'=>array('file.txt'))));
     482                // or
     483                //   $foo['path']['to']['my'] = 'file.txt';
     484                $ArrayPath = ltrim($ArrayPath, $Separator);
     485                if (($pos = strpos($ArrayPath, $Separator)) !== false) {
     486                        $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
     487                } else {
     488                        $ReturnedArray[$ArrayPath] = $Value;
     489                }
     490                return $ReturnedArray;
     491        }
     492
     493        public static function array_max($arraydata, $returnkey=false) {
     494                $maxvalue = false;
     495                $maxkey = false;
     496                foreach ($arraydata as $key => $value) {
     497                        if (!is_array($value)) {
     498                                if ($value > $maxvalue) {
     499                                        $maxvalue = $value;
     500                                        $maxkey = $key;
     501                                }
     502                        }
     503                }
     504                return ($returnkey ? $maxkey : $maxvalue);
     505        }
     506
     507        public static function array_min($arraydata, $returnkey=false) {
     508                $minvalue = false;
     509                $minkey = false;
     510                foreach ($arraydata as $key => $value) {
     511                        if (!is_array($value)) {
     512                                if ($value > $minvalue) {
     513                                        $minvalue = $value;
     514                                        $minkey = $key;
     515                                }
     516                        }
     517                }
     518                return ($returnkey ? $minkey : $minvalue);
     519        }
     520
     521        public static function XML2array($XMLstring) {
     522                if (function_exists('simplexml_load_string')) {
     523                        if (function_exists('get_object_vars')) {
     524                                $XMLobject = simplexml_load_string($XMLstring);
     525                                return self::SimpleXMLelement2array($XMLobject);
     526                        }
     527                }
     528                return false;
     529        }
     530
     531        public static function SimpleXMLelement2array($XMLobject) {
     532                if (!is_object($XMLobject) && !is_array($XMLobject)) {
     533                        return $XMLobject;
     534                }
     535                $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject);
     536                foreach ($XMLarray as $key => $value) {
     537                        $XMLarray[$key] = self::SimpleXMLelement2array($value);
     538                }
     539                return $XMLarray;
     540        }
     541
     542
     543        // Allan Hansen <ahØartemis*dk>
     544        // self::md5_data() - returns md5sum for a file from startuing position to absolute end position
     545        public static function hash_data($file, $offset, $end, $algorithm) {
     546                static $tempdir = '';
     547                if (!self::intValueSupported($end)) {
     548                        return false;
     549                }
     550                switch ($algorithm) {
     551                        case 'md5':
     552                                $hash_function = 'md5_file';
     553                                $unix_call     = 'md5sum';
     554                                $windows_call  = 'md5sum.exe';
     555                                $hash_length   = 32;
     556                                break;
     557
     558                        case 'sha1':
     559                                $hash_function = 'sha1_file';
     560                                $unix_call     = 'sha1sum';
     561                                $windows_call  = 'sha1sum.exe';
     562                                $hash_length   = 40;
     563                                break;
     564
     565                        default:
     566                                throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()');
     567                                break;
     568                }
     569                $size = $end - $offset;
     570                while (true) {
     571                        if (GETID3_OS_ISWINDOWS) {
     572
     573                                // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data
     574                                // Fall back to create-temp-file method:
     575                                if ($algorithm == 'sha1') {
     576                                        break;
     577                                }
     578
     579                                $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call);
     580                                foreach ($RequiredFiles as $required_file) {
     581                                        if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
     582                                                // helper apps not available - fall back to old method
     583                                                break 2;
     584                                        }
     585                                }
     586                                $commandline  = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | ';
     587                                $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | ';
     588                                $commandline .= GETID3_HELPERAPPSDIR.$windows_call;
     589
     590                        } else {
     591
     592                                $commandline  = 'head -c'.$end.' '.escapeshellarg($file).' | ';
     593                                $commandline .= 'tail -c'.$size.' | ';
     594                                $commandline .= $unix_call;
     595
     596                        }
     597                        if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
     598                                //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm');
     599                                break;
     600                        }
     601                        return substr(`$commandline`, 0, $hash_length);
     602                }
     603
     604                if (empty($tempdir)) {
     605                        // yes this is ugly, feel free to suggest a better way
     606                        require_once(dirname(__FILE__).'/getid3.php');
     607                        $getid3_temp = new getID3();
     608                        $tempdir = $getid3_temp->tempdir;
     609                        unset($getid3_temp);
     610                }
     611                // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir
     612                if (($data_filename = tempnam($tempdir, 'gI3')) === false) {
     613                        // can't find anywhere to create a temp file, just fail
     614                        return false;
     615                }
     616
     617                // Init
     618                $result = false;
     619
     620                // copy parts of file
     621                try {
     622                        self::CopyFileParts($file, $data_filename, $offset, $end - $offset);
     623                        $result = $hash_function($data_filename);
     624                } catch (Exception $e) {
     625                        throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage());
     626                }
     627                unlink($data_filename);
     628                return $result;
     629        }
     630
     631        public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) {
     632                if (!self::intValueSupported($offset + $length)) {
     633                        throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit');
     634                }
     635                if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) {
     636                        if (($fp_dest = fopen($filename_dest, 'wb'))) {
     637                                if (fseek($fp_src, $offset, SEEK_SET) == 0) {
     638                                        $byteslefttowrite = $length;
     639                                        while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) {
     640                                                $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite);
     641                                                $byteslefttowrite -= $byteswritten;
     642                                        }
     643                                        return true;
     644                                } else {
     645                                        throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source);
     646                                }
     647                                fclose($fp_dest);
     648                        } else {
     649                                throw new Exception('failed to create file for writing '.$filename_dest);
     650                        }
     651                        fclose($fp_src);
     652                } else {
     653                        throw new Exception('failed to open file for reading '.$filename_source);
     654                }
     655                return false;
     656        }
     657
     658        public static function iconv_fallback_int_utf8($charval) {
     659                if ($charval < 128) {
     660                        // 0bbbbbbb
     661                        $newcharstring = chr($charval);
     662                } elseif ($charval < 2048) {
     663                        // 110bbbbb 10bbbbbb
     664                        $newcharstring  = chr(($charval >>   6) | 0xC0);
     665                        $newcharstring .= chr(($charval & 0x3F) | 0x80);
     666                } elseif ($charval < 65536) {
     667                        // 1110bbbb 10bbbbbb 10bbbbbb
     668                        $newcharstring  = chr(($charval >>  12) | 0xE0);
     669                        $newcharstring .= chr(($charval >>   6) | 0xC0);
     670                        $newcharstring .= chr(($charval & 0x3F) | 0x80);
     671                } else {
     672                        // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
     673                        $newcharstring  = chr(($charval >>  18) | 0xF0);
     674                        $newcharstring .= chr(($charval >>  12) | 0xC0);
     675                        $newcharstring .= chr(($charval >>   6) | 0xC0);
     676                        $newcharstring .= chr(($charval & 0x3F) | 0x80);
     677                }
     678                return $newcharstring;
     679        }
     680
     681        // ISO-8859-1 => UTF-8
     682        public static function iconv_fallback_iso88591_utf8($string, $bom=false) {
     683                if (function_exists('utf8_encode')) {
     684                        return utf8_encode($string);
     685                }
     686                // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
     687                $newcharstring = '';
     688                if ($bom) {
     689                        $newcharstring .= "\xEF\xBB\xBF";
     690                }
     691                for ($i = 0; $i < strlen($string); $i++) {
     692                        $charval = ord($string{$i});
     693                        $newcharstring .= self::iconv_fallback_int_utf8($charval);
     694                }
     695                return $newcharstring;
     696        }
     697
     698        // ISO-8859-1 => UTF-16BE
     699        public static function iconv_fallback_iso88591_utf16be($string, $bom=false) {
     700                $newcharstring = '';
     701                if ($bom) {
     702                        $newcharstring .= "\xFE\xFF";
     703                }
     704                for ($i = 0; $i < strlen($string); $i++) {
     705                        $newcharstring .= "\x00".$string{$i};
     706                }
     707                return $newcharstring;
     708        }
     709
     710        // ISO-8859-1 => UTF-16LE
     711        public static function iconv_fallback_iso88591_utf16le($string, $bom=false) {
     712                $newcharstring = '';
     713                if ($bom) {
     714                        $newcharstring .= "\xFF\xFE";
     715                }
     716                for ($i = 0; $i < strlen($string); $i++) {
     717                        $newcharstring .= $string{$i}."\x00";
     718                }
     719                return $newcharstring;
     720        }
     721
     722        // ISO-8859-1 => UTF-16LE (BOM)
     723        public static function iconv_fallback_iso88591_utf16($string) {
     724                return self::iconv_fallback_iso88591_utf16le($string, true);
     725        }
     726
     727        // UTF-8 => ISO-8859-1
     728        public static function iconv_fallback_utf8_iso88591($string) {
     729                if (function_exists('utf8_decode')) {
     730                        return utf8_decode($string);
     731                }
     732                // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
     733                $newcharstring = '';
     734                $offset = 0;
     735                $stringlength = strlen($string);
     736                while ($offset < $stringlength) {
     737                        if ((ord($string{$offset}) | 0x07) == 0xF7) {
     738                                // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
     739                                $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
     740                                                   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
     741                                                   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
     742                                                        (ord($string{($offset + 3)}) & 0x3F);
     743                                $offset += 4;
     744                        } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
     745                                // 1110bbbb 10bbbbbb 10bbbbbb
     746                                $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
     747                                                   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
     748                                                        (ord($string{($offset + 2)}) & 0x3F);
     749                                $offset += 3;
     750                        } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
     751                                // 110bbbbb 10bbbbbb
     752                                $charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
     753                                                        (ord($string{($offset + 1)}) & 0x3F);
     754                                $offset += 2;
     755                        } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
     756                                // 0bbbbbbb
     757                                $charval = ord($string{$offset});
     758                                $offset += 1;
     759                        } else {
     760                                // error? throw some kind of warning here?
     761                                $charval = false;
     762                                $offset += 1;
     763                        }
     764                        if ($charval !== false) {
     765                                $newcharstring .= (($charval < 256) ? chr($charval) : '?');
     766                        }
     767                }
     768                return $newcharstring;
     769        }
     770
     771        // UTF-8 => UTF-16BE
     772        public static function iconv_fallback_utf8_utf16be($string, $bom=false) {
     773                $newcharstring = '';
     774                if ($bom) {
     775                        $newcharstring .= "\xFE\xFF";
     776                }
     777                $offset = 0;
     778                $stringlength = strlen($string);
     779                while ($offset < $stringlength) {
     780                        if ((ord($string{$offset}) | 0x07) == 0xF7) {
     781                                // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
     782                                $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
     783                                                   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
     784                                                   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
     785                                                        (ord($string{($offset + 3)}) & 0x3F);
     786                                $offset += 4;
     787                        } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
     788                                // 1110bbbb 10bbbbbb 10bbbbbb
     789                                $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
     790                                                   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
     791                                                        (ord($string{($offset + 2)}) & 0x3F);
     792                                $offset += 3;
     793                        } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
     794                                // 110bbbbb 10bbbbbb
     795                                $charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
     796                                                        (ord($string{($offset + 1)}) & 0x3F);
     797                                $offset += 2;
     798                        } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
     799                                // 0bbbbbbb
     800                                $charval = ord($string{$offset});
     801                                $offset += 1;
     802                        } else {
     803                                // error? throw some kind of warning here?
     804                                $charval = false;
     805                                $offset += 1;
     806                        }
     807                        if ($charval !== false) {
     808                                $newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?');
     809                        }
     810                }
     811                return $newcharstring;
     812        }
     813
     814        // UTF-8 => UTF-16LE
     815        public static function iconv_fallback_utf8_utf16le($string, $bom=false) {
     816                $newcharstring = '';
     817                if ($bom) {
     818                        $newcharstring .= "\xFF\xFE";
     819                }
     820                $offset = 0;
     821                $stringlength = strlen($string);
     822                while ($offset < $stringlength) {
     823                        if ((ord($string{$offset}) | 0x07) == 0xF7) {
     824                                // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
     825                                $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
     826                                                   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
     827                                                   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
     828                                                        (ord($string{($offset + 3)}) & 0x3F);
     829                                $offset += 4;
     830                        } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
     831                                // 1110bbbb 10bbbbbb 10bbbbbb
     832                                $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
     833                                                   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
     834                                                        (ord($string{($offset + 2)}) & 0x3F);
     835                                $offset += 3;
     836                        } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
     837                                // 110bbbbb 10bbbbbb
     838                                $charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
     839                                                        (ord($string{($offset + 1)}) & 0x3F);
     840                                $offset += 2;
     841                        } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
     842                                // 0bbbbbbb
     843                                $charval = ord($string{$offset});
     844                                $offset += 1;
     845                        } else {
     846                                // error? maybe throw some warning here?
     847                                $charval = false;
     848                                $offset += 1;
     849                        }
     850                        if ($charval !== false) {
     851                                $newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00");
     852                        }
     853                }
     854                return $newcharstring;
     855        }
     856
     857        // UTF-8 => UTF-16LE (BOM)
     858        public static function iconv_fallback_utf8_utf16($string) {
     859                return self::iconv_fallback_utf8_utf16le($string, true);
     860        }
     861
     862        // UTF-16BE => UTF-8
     863        public static function iconv_fallback_utf16be_utf8($string) {
     864                if (substr($string, 0, 2) == "\xFE\xFF") {
     865                        // strip BOM
     866                        $string = substr($string, 2);
     867                }
     868                $newcharstring = '';
     869                for ($i = 0; $i < strlen($string); $i += 2) {
     870                        $charval = self::BigEndian2Int(substr($string, $i, 2));
     871                        $newcharstring .= self::iconv_fallback_int_utf8($charval);
     872                }
     873                return $newcharstring;
     874        }
     875
     876        // UTF-16LE => UTF-8
     877        public static function iconv_fallback_utf16le_utf8($string) {
     878                if (substr($string, 0, 2) == "\xFF\xFE") {
     879                        // strip BOM
     880                        $string = substr($string, 2);
     881                }
     882                $newcharstring = '';
     883                for ($i = 0; $i < strlen($string); $i += 2) {
     884                        $charval = self::LittleEndian2Int(substr($string, $i, 2));
     885                        $newcharstring .= self::iconv_fallback_int_utf8($charval);
     886                }
     887                return $newcharstring;
     888        }
     889
     890        // UTF-16BE => ISO-8859-1
     891        public static function iconv_fallback_utf16be_iso88591($string) {
     892                if (substr($string, 0, 2) == "\xFE\xFF") {
     893                        // strip BOM
     894                        $string = substr($string, 2);
     895                }
     896                $newcharstring = '';
     897                for ($i = 0; $i < strlen($string); $i += 2) {
     898                        $charval = self::BigEndian2Int(substr($string, $i, 2));
     899                        $newcharstring .= (($charval < 256) ? chr($charval) : '?');
     900                }
     901                return $newcharstring;
     902        }
     903
     904        // UTF-16LE => ISO-8859-1
     905        public static function iconv_fallback_utf16le_iso88591($string) {
     906                if (substr($string, 0, 2) == "\xFF\xFE") {
     907                        // strip BOM
     908                        $string = substr($string, 2);
     909                }
     910                $newcharstring = '';
     911                for ($i = 0; $i < strlen($string); $i += 2) {
     912                        $charval = self::LittleEndian2Int(substr($string, $i, 2));
     913                        $newcharstring .= (($charval < 256) ? chr($charval) : '?');
     914                }
     915                return $newcharstring;
     916        }
     917
     918        // UTF-16 (BOM) => ISO-8859-1
     919        public static function iconv_fallback_utf16_iso88591($string) {
     920                $bom = substr($string, 0, 2);
     921                if ($bom == "\xFE\xFF") {
     922                        return self::iconv_fallback_utf16be_iso88591(substr($string, 2));
     923                } elseif ($bom == "\xFF\xFE") {
     924                        return self::iconv_fallback_utf16le_iso88591(substr($string, 2));
     925                }
     926                return $string;
     927        }
     928
     929        // UTF-16 (BOM) => UTF-8
     930        public static function iconv_fallback_utf16_utf8($string) {
     931                $bom = substr($string, 0, 2);
     932                if ($bom == "\xFE\xFF") {
     933                        return self::iconv_fallback_utf16be_utf8(substr($string, 2));
     934                } elseif ($bom == "\xFF\xFE") {
     935                        return self::iconv_fallback_utf16le_utf8(substr($string, 2));
     936                }
     937                return $string;
     938        }
     939
     940        public static function iconv_fallback($in_charset, $out_charset, $string) {
     941
     942                if ($in_charset == $out_charset) {
     943                        return $string;
     944                }
     945
     946                // iconv() availble
     947                if (function_exists('iconv')) {
     948                        if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) {
     949                                switch ($out_charset) {
     950                                        case 'ISO-8859-1':
     951                                                $converted_string = rtrim($converted_string, "\x00");
     952                                                break;
     953                                }
     954                                return $converted_string;
     955                        }
     956
     957                        // iconv() may sometimes fail with "illegal character in input string" error message
     958                        // and return an empty string, but returning the unconverted string is more useful
     959                        return $string;
     960                }
     961
     962
     963                // iconv() not available
     964                static $ConversionFunctionList = array();
     965                if (empty($ConversionFunctionList)) {
     966                        $ConversionFunctionList['ISO-8859-1']['UTF-8']    = 'iconv_fallback_iso88591_utf8';
     967                        $ConversionFunctionList['ISO-8859-1']['UTF-16']   = 'iconv_fallback_iso88591_utf16';
     968                        $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be';
     969                        $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le';
     970                        $ConversionFunctionList['UTF-8']['ISO-8859-1']    = 'iconv_fallback_utf8_iso88591';
     971                        $ConversionFunctionList['UTF-8']['UTF-16']        = 'iconv_fallback_utf8_utf16';
     972                        $ConversionFunctionList['UTF-8']['UTF-16BE']      = 'iconv_fallback_utf8_utf16be';
     973                        $ConversionFunctionList['UTF-8']['UTF-16LE']      = 'iconv_fallback_utf8_utf16le';
     974                        $ConversionFunctionList['UTF-16']['ISO-8859-1']   = 'iconv_fallback_utf16_iso88591';
     975                        $ConversionFunctionList['UTF-16']['UTF-8']        = 'iconv_fallback_utf16_utf8';
     976                        $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591';
     977                        $ConversionFunctionList['UTF-16LE']['UTF-8']      = 'iconv_fallback_utf16le_utf8';
     978                        $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591';
     979                        $ConversionFunctionList['UTF-16BE']['UTF-8']      = 'iconv_fallback_utf16be_utf8';
     980                }
     981                if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) {
     982                        $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)];
     983                        return self::$ConversionFunction($string);
     984                }
     985                throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
     986        }
     987
     988
     989        public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') {
     990                $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string
     991                $HTMLstring = '';
     992
     993                switch ($charset) {
     994                        case '1251':
     995                        case '1252':
     996                        case '866':
     997                        case '932':
     998                        case '936':
     999                        case '950':
     1000                        case 'BIG5':
     1001                        case 'BIG5-HKSCS':
     1002                        case 'cp1251':
     1003                        case 'cp1252':
     1004                        case 'cp866':
     1005                        case 'EUC-JP':
     1006                        case 'EUCJP':
     1007                        case 'GB2312':
     1008                        case 'ibm866':
     1009                        case 'ISO-8859-1':
     1010                        case 'ISO-8859-15':
     1011                        case 'ISO8859-1':
     1012                        case 'ISO8859-15':
     1013                        case 'KOI8-R':
     1014                        case 'koi8-ru':
     1015                        case 'koi8r':
     1016                        case 'Shift_JIS':
     1017                        case 'SJIS':
     1018                        case 'win-1251':
     1019                        case 'Windows-1251':
     1020                        case 'Windows-1252':
     1021                                $HTMLstring = htmlentities($string, ENT_COMPAT, $charset);
     1022                                break;
     1023
     1024                        case 'UTF-8':
     1025                                $strlen = strlen($string);
     1026                                for ($i = 0; $i < $strlen; $i++) {
     1027                                        $char_ord_val = ord($string{$i});
     1028                                        $charval = 0;
     1029                                        if ($char_ord_val < 0x80) {
     1030                                                $charval = $char_ord_val;
     1031                                        } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F  &&  $i+3 < $strlen) {
     1032                                                $charval  = (($char_ord_val & 0x07) << 18);
     1033                                                $charval += ((ord($string{++$i}) & 0x3F) << 12);
     1034                                                $charval += ((ord($string{++$i}) & 0x3F) << 6);
     1035                                                $charval +=  (ord($string{++$i}) & 0x3F);
     1036                                        } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07  &&  $i+2 < $strlen) {
     1037                                                $charval  = (($char_ord_val & 0x0F) << 12);
     1038                                                $charval += ((ord($string{++$i}) & 0x3F) << 6);
     1039                                                $charval +=  (ord($string{++$i}) & 0x3F);
     1040                                        } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03  &&  $i+1 < $strlen) {
     1041                                                $charval  = (($char_ord_val & 0x1F) << 6);
     1042                                                $charval += (ord($string{++$i}) & 0x3F);
     1043                                        }
     1044                                        if (($charval >= 32) && ($charval <= 127)) {
     1045                                                $HTMLstring .= htmlentities(chr($charval));
     1046                                        } else {
     1047                                                $HTMLstring .= '&#'.$charval.';';
     1048                                        }
     1049                                }
     1050                                break;
     1051
     1052                        case 'UTF-16LE':
     1053                                for ($i = 0; $i < strlen($string); $i += 2) {
     1054                                        $charval = self::LittleEndian2Int(substr($string, $i, 2));
     1055                                        if (($charval >= 32) && ($charval <= 127)) {
     1056                                                $HTMLstring .= chr($charval);
     1057                                        } else {
     1058                                                $HTMLstring .= '&#'.$charval.';';
     1059                                        }
     1060                                }
     1061                                break;
     1062
     1063                        case 'UTF-16BE':
     1064                                for ($i = 0; $i < strlen($string); $i += 2) {
     1065                                        $charval = self::BigEndian2Int(substr($string, $i, 2));
     1066                                        if (($charval >= 32) && ($charval <= 127)) {
     1067                                                $HTMLstring .= chr($charval);
     1068                                        } else {
     1069                                                $HTMLstring .= '&#'.$charval.';';
     1070                                        }
     1071                                }
     1072                                break;
     1073
     1074                        default:
     1075                                $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()';
     1076                                break;
     1077                }
     1078                return $HTMLstring;
     1079        }
     1080
     1081
     1082
     1083        public static function RGADnameLookup($namecode) {
     1084                static $RGADname = array();
     1085                if (empty($RGADname)) {
     1086                        $RGADname[0] = 'not set';
     1087                        $RGADname[1] = 'Track Gain Adjustment';
     1088                        $RGADname[2] = 'Album Gain Adjustment';
     1089                }
     1090
     1091                return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : '');
     1092        }
     1093
     1094
     1095        public static function RGADoriginatorLookup($originatorcode) {
     1096                static $RGADoriginator = array();
     1097                if (empty($RGADoriginator)) {
     1098                        $RGADoriginator[0] = 'unspecified';
     1099                        $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer';
     1100                        $RGADoriginator[2] = 'set by user';
     1101                        $RGADoriginator[3] = 'determined automatically';
     1102                }
     1103
     1104                return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : '');
     1105        }
     1106
     1107
     1108        public static function RGADadjustmentLookup($rawadjustment, $signbit) {
     1109                $adjustment = $rawadjustment / 10;
     1110                if ($signbit == 1) {
     1111                        $adjustment *= -1;
     1112                }
     1113                return (float) $adjustment;
     1114        }
     1115
     1116
     1117        public static function RGADgainString($namecode, $originatorcode, $replaygain) {
     1118                if ($replaygain < 0) {
     1119                        $signbit = '1';
     1120                } else {
     1121                        $signbit = '0';
     1122                }
     1123                $storedreplaygain = intval(round($replaygain * 10));
     1124                $gainstring  = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT);
     1125                $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT);
     1126                $gainstring .= $signbit;
     1127                $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT);
     1128
     1129                return $gainstring;
     1130        }
     1131
     1132        public static function RGADamplitude2dB($amplitude) {
     1133                return 20 * log10($amplitude);
     1134        }
     1135
     1136
     1137        public static function GetDataImageSize($imgData, &$imageinfo=array()) {
     1138                static $tempdir = '';
     1139                if (empty($tempdir)) {
     1140                        // yes this is ugly, feel free to suggest a better way
     1141                        require_once(dirname(__FILE__).'/class-getid3.php');
     1142                        $getid3_temp = new getID3();
     1143                        $tempdir = $getid3_temp->tempdir;
     1144                        unset($getid3_temp);
     1145                }
     1146                $GetDataImageSize = false;
     1147                if ($tempfilename = tempnam($tempdir, 'gI3')) {
     1148                        if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) {
     1149                                fwrite($tmp, $imgData);
     1150                                fclose($tmp);
     1151                                $GetDataImageSize = @getimagesize($tempfilename, $imageinfo);
     1152                        }
     1153                        unlink($tempfilename);
     1154                }
     1155                return $GetDataImageSize;
     1156        }
     1157
     1158        public static function ImageExtFromMime($mime_type) {
     1159                // temporary way, works OK for now, but should be reworked in the future
     1160                return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type);
     1161        }
     1162
     1163        public static function ImageTypesLookup($imagetypeid) {
     1164                static $ImageTypesLookup = array();
     1165                if (empty($ImageTypesLookup)) {
     1166                        $ImageTypesLookup[1]  = 'gif';
     1167                        $ImageTypesLookup[2]  = 'jpeg';
     1168                        $ImageTypesLookup[3]  = 'png';
     1169                        $ImageTypesLookup[4]  = 'swf';
     1170                        $ImageTypesLookup[5]  = 'psd';
     1171                        $ImageTypesLookup[6]  = 'bmp';
     1172                        $ImageTypesLookup[7]  = 'tiff (little-endian)';
     1173                        $ImageTypesLookup[8]  = 'tiff (big-endian)';
     1174                        $ImageTypesLookup[9]  = 'jpc';
     1175                        $ImageTypesLookup[10] = 'jp2';
     1176                        $ImageTypesLookup[11] = 'jpx';
     1177                        $ImageTypesLookup[12] = 'jb2';
     1178                        $ImageTypesLookup[13] = 'swc';
     1179                        $ImageTypesLookup[14] = 'iff';
     1180                }
     1181                return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : '');
     1182        }
     1183
     1184        public static function CopyTagsToComments(&$ThisFileInfo) {
     1185
     1186                // Copy all entries from ['tags'] into common ['comments']
     1187                if (!empty($ThisFileInfo['tags'])) {
     1188                        foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
     1189                                foreach ($tagarray as $tagname => $tagdata) {
     1190                                        foreach ($tagdata as $key => $value) {
     1191                                                if (!empty($value)) {
     1192                                                        if (empty($ThisFileInfo['comments'][$tagname])) {
     1193
     1194                                                                // fall through and append value
     1195
     1196                                                        } elseif ($tagtype == 'id3v1') {
     1197
     1198                                                                $newvaluelength = strlen(trim($value));
     1199                                                                foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
     1200                                                                        $oldvaluelength = strlen(trim($existingvalue));
     1201                                                                        if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) {
     1202                                                                                // new value is identical but shorter-than (or equal-length to) one already in comments - skip
     1203                                                                                break 2;
     1204                                                                        }
     1205                                                                }
     1206
     1207                                                        } elseif (!is_array($value)) {
     1208
     1209                                                                $newvaluelength = strlen(trim($value));
     1210                                                                foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
     1211                                                                        $oldvaluelength = strlen(trim($existingvalue));
     1212                                                                        if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
     1213                                                                                $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
     1214                                                                                break 2;
     1215                                                                        }
     1216                                                                }
     1217
     1218                                                        }
     1219                                                        if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) {
     1220                                                                $value = (is_string($value) ? trim($value) : $value);
     1221                                                                $ThisFileInfo['comments'][$tagname][] = $value;
     1222                                                        }
     1223                                                }
     1224                                        }
     1225                                }
     1226                        }
     1227
     1228                        // Copy to ['comments_html']
     1229                        foreach ($ThisFileInfo['comments'] as $field => $values) {
     1230                                if ($field == 'picture') {
     1231                                        // pictures can take up a lot of space, and we don't need multiple copies of them
     1232                                        // let there be a single copy in [comments][picture], and not elsewhere
     1233                                        continue;
     1234                                }
     1235                                foreach ($values as $index => $value) {
     1236                                        if (is_array($value)) {
     1237                                                $ThisFileInfo['comments_html'][$field][$index] = $value;
     1238                                        } else {
     1239                                                $ThisFileInfo['comments_html'][$field][$index] = str_replace('&#0;', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding']));
     1240                                        }
     1241                                }
     1242                        }
     1243                }
     1244                return true;
     1245        }
     1246
     1247
     1248        public static function EmbeddedLookup($key, $begin, $end, $file, $name) {
     1249
     1250                // Cached
     1251                static $cache;
     1252                if (isset($cache[$file][$name])) {
     1253                        return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
     1254                }
     1255
     1256                // Init
     1257                $keylength  = strlen($key);
     1258                $line_count = $end - $begin - 7;
     1259
     1260                // Open php file
     1261                $fp = fopen($file, 'r');
     1262
     1263                // Discard $begin lines
     1264                for ($i = 0; $i < ($begin + 3); $i++) {
     1265                        fgets($fp, 1024);
     1266                }
     1267
     1268                // Loop thru line
     1269                while (0 < $line_count--) {
     1270
     1271                        // Read line
     1272                        $line = ltrim(fgets($fp, 1024), "\t ");
     1273
     1274                        // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key
     1275                        //$keycheck = substr($line, 0, $keylength);
     1276                        //if ($key == $keycheck)  {
     1277                        //      $cache[$file][$name][$keycheck] = substr($line, $keylength + 1);
     1278                        //      break;
     1279                        //}
     1280
     1281                        // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key
     1282                        //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1));
     1283                        $explodedLine = explode("\t", $line, 2);
     1284                        $ThisKey   = (isset($explodedLine[0]) ? $explodedLine[0] : '');
     1285                        $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : '');
     1286                        $cache[$file][$name][$ThisKey] = trim($ThisValue);
     1287                }
     1288
     1289                // Close and return
     1290                fclose($fp);
     1291                return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
     1292        }
     1293
     1294        public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) {
     1295                global $GETID3_ERRORARRAY;
     1296
     1297                if (file_exists($filename)) {
     1298                        if (include_once($filename)) {
     1299                                return true;
     1300                        } else {
     1301                                $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors';
     1302                        }
     1303                } else {
     1304                        $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing';
     1305                }
     1306                if ($DieOnFailure) {
     1307                        throw new Exception($diemessage);
     1308                } else {
     1309                        $GETID3_ERRORARRAY[] = $diemessage;
     1310                }
     1311                return false;
     1312        }
     1313
     1314        public static function trimNullByte($string) {
     1315                return trim($string, "\x00");
     1316        }
     1317
     1318        public static function getFileSizeSyscall($path) {
     1319                $filesize = false;
     1320
     1321                if (GETID3_OS_ISWINDOWS) {
     1322                        if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini:
     1323                                $filesystem = new COM('Scripting.FileSystemObject');
     1324                                $file = $filesystem->GetFile($path);
     1325                                $filesize = $file->Size();
     1326                                unset($filesystem, $file);
     1327                        } else {
     1328                                $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI';
     1329                        }
     1330                } else {
     1331                        $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\'';
     1332                }
     1333                if (isset($commandline)) {
     1334                        $output = trim(`$commandline`);
     1335                        if (ctype_digit($output)) {
     1336                                $filesize = (float) $output;
     1337                        }
     1338                }
     1339                return $filesize;
     1340        }
     1341}
     1342 No newline at end of file
  • new file wp-includes/ID3/module.audio-video.asf.php

    diff --git wp-includes/ID3/module.audio-video.asf.php wp-includes/ID3/module.audio-video.asf.php
    new file mode 100644
    index 0000000..cfc60a7
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio-video.asf.php                                  //
     11// module for analyzing ASF, WMA and WMV files                 //
     12// dependencies: module.audio-video.riff.php                   //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
     17
     18class getid3_asf extends getid3_handler
     19{
     20
     21        public function __construct(getID3 $getid3) {
     22                parent::__construct($getid3);  // extends getid3_handler::__construct()
     23
     24                // initialize all GUID constants
     25                $GUIDarray = $this->KnownGUIDs();
     26                foreach ($GUIDarray as $GUIDname => $hexstringvalue) {
     27                        if (!defined($GUIDname)) {
     28                                define($GUIDname, $this->GUIDtoBytestring($hexstringvalue));
     29                        }
     30                }
     31        }
     32
     33        public function Analyze() {
     34                $info = &$this->getid3->info;
     35
     36                // Shortcuts
     37                $thisfile_audio = &$info['audio'];
     38                $thisfile_video = &$info['video'];
     39                $info['asf']  = array();
     40                $thisfile_asf = &$info['asf'];
     41                $thisfile_asf['comments'] = array();
     42                $thisfile_asf_comments    = &$thisfile_asf['comments'];
     43                $thisfile_asf['header_object'] = array();
     44                $thisfile_asf_headerobject     = &$thisfile_asf['header_object'];
     45
     46
     47                // ASF structure:
     48                // * Header Object [required]
     49                //   * File Properties Object [required]   (global file attributes)
     50                //   * Stream Properties Object [required] (defines media stream & characteristics)
     51                //   * Header Extension Object [required]  (additional functionality)
     52                //   * Content Description Object          (bibliographic information)
     53                //   * Script Command Object               (commands for during playback)
     54                //   * Marker Object                       (named jumped points within the file)
     55                // * Data Object [required]
     56                //   * Data Packets
     57                // * Index Object
     58
     59                // Header Object: (mandatory, one only)
     60                // Field Name                   Field Type   Size (bits)
     61                // Object ID                    GUID         128             // GUID for header object - GETID3_ASF_Header_Object
     62                // Object Size                  QWORD        64              // size of header object, including 30 bytes of Header Object header
     63                // Number of Header Objects     DWORD        32              // number of objects in header object
     64                // Reserved1                    BYTE         8               // hardcoded: 0x01
     65                // Reserved2                    BYTE         8               // hardcoded: 0x02
     66
     67                $info['fileformat'] = 'asf';
     68
     69                fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     70                $HeaderObjectData = fread($this->getid3->fp, 30);
     71
     72                $thisfile_asf_headerobject['objectid']      = substr($HeaderObjectData, 0, 16);
     73                $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']);
     74                if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) {
     75                        $info['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}';
     76                        unset($info['fileformat']);
     77                        unset($info['asf']);
     78                        return false;
     79                        break;
     80                }
     81                $thisfile_asf_headerobject['objectsize']    = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8));
     82                $thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4));
     83                $thisfile_asf_headerobject['reserved1']     = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1));
     84                $thisfile_asf_headerobject['reserved2']     = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1));
     85
     86                $NextObjectOffset = ftell($this->getid3->fp);
     87                $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30);
     88                $offset = 0;
     89
     90                for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) {
     91                        $NextObjectGUID = substr($ASFHeaderData, $offset, 16);
     92                        $offset += 16;
     93                        $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID);
     94                        $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     95                        $offset += 8;
     96                        switch ($NextObjectGUID) {
     97
     98                                case GETID3_ASF_File_Properties_Object:
     99                                        // File Properties Object: (mandatory, one only)
     100                                        // Field Name                   Field Type   Size (bits)
     101                                        // Object ID                    GUID         128             // GUID for file properties object - GETID3_ASF_File_Properties_Object
     102                                        // Object Size                  QWORD        64              // size of file properties object, including 104 bytes of File Properties Object header
     103                                        // File ID                      GUID         128             // unique ID - identical to File ID in Data Object
     104                                        // File Size                    QWORD        64              // entire file in bytes. Invalid if Broadcast Flag == 1
     105                                        // Creation Date                QWORD        64              // date & time of file creation. Maybe invalid if Broadcast Flag == 1
     106                                        // Data Packets Count           QWORD        64              // number of data packets in Data Object. Invalid if Broadcast Flag == 1
     107                                        // Play Duration                QWORD        64              // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1
     108                                        // Send Duration                QWORD        64              // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1
     109                                        // Preroll                      QWORD        64              // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount
     110                                        // Flags                        DWORD        32              //
     111                                        // * Broadcast Flag             bits         1  (0x01)       // file is currently being written, some header values are invalid
     112                                        // * Seekable Flag              bits         1  (0x02)       // is file seekable
     113                                        // * Reserved                   bits         30 (0xFFFFFFFC) // reserved - set to zero
     114                                        // Minimum Data Packet Size     DWORD        32              // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1
     115                                        // Maximum Data Packet Size     DWORD        32              // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1
     116                                        // Maximum Bitrate              DWORD        32              // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead
     117
     118                                        // shortcut
     119                                        $thisfile_asf['file_properties_object'] = array();
     120                                        $thisfile_asf_filepropertiesobject      = &$thisfile_asf['file_properties_object'];
     121
     122                                        $thisfile_asf_filepropertiesobject['offset']             = $NextObjectOffset + $offset;
     123                                        $thisfile_asf_filepropertiesobject['objectid']           = $NextObjectGUID;
     124                                        $thisfile_asf_filepropertiesobject['objectid_guid']      = $NextObjectGUIDtext;
     125                                        $thisfile_asf_filepropertiesobject['objectsize']         = $NextObjectSize;
     126                                        $thisfile_asf_filepropertiesobject['fileid']             = substr($ASFHeaderData, $offset, 16);
     127                                        $offset += 16;
     128                                        $thisfile_asf_filepropertiesobject['fileid_guid']        = $this->BytestringToGUID($thisfile_asf_filepropertiesobject['fileid']);
     129                                        $thisfile_asf_filepropertiesobject['filesize']           = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     130                                        $offset += 8;
     131                                        $thisfile_asf_filepropertiesobject['creation_date']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     132                                        $thisfile_asf_filepropertiesobject['creation_date_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_filepropertiesobject['creation_date']);
     133                                        $offset += 8;
     134                                        $thisfile_asf_filepropertiesobject['data_packets']       = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     135                                        $offset += 8;
     136                                        $thisfile_asf_filepropertiesobject['play_duration']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     137                                        $offset += 8;
     138                                        $thisfile_asf_filepropertiesobject['send_duration']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     139                                        $offset += 8;
     140                                        $thisfile_asf_filepropertiesobject['preroll']            = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     141                                        $offset += 8;
     142                                        $thisfile_asf_filepropertiesobject['flags_raw']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     143                                        $offset += 4;
     144                                        $thisfile_asf_filepropertiesobject['flags']['broadcast'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0001);
     145                                        $thisfile_asf_filepropertiesobject['flags']['seekable']  = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0002);
     146
     147                                        $thisfile_asf_filepropertiesobject['min_packet_size']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     148                                        $offset += 4;
     149                                        $thisfile_asf_filepropertiesobject['max_packet_size']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     150                                        $offset += 4;
     151                                        $thisfile_asf_filepropertiesobject['max_bitrate']        = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     152                                        $offset += 4;
     153
     154                                        if ($thisfile_asf_filepropertiesobject['flags']['broadcast']) {
     155
     156                                                // broadcast flag is set, some values invalid
     157                                                unset($thisfile_asf_filepropertiesobject['filesize']);
     158                                                unset($thisfile_asf_filepropertiesobject['data_packets']);
     159                                                unset($thisfile_asf_filepropertiesobject['play_duration']);
     160                                                unset($thisfile_asf_filepropertiesobject['send_duration']);
     161                                                unset($thisfile_asf_filepropertiesobject['min_packet_size']);
     162                                                unset($thisfile_asf_filepropertiesobject['max_packet_size']);
     163
     164                                        } else {
     165
     166                                                // broadcast flag NOT set, perform calculations
     167                                                $info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000);
     168
     169                                                //$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate'];
     170                                                $info['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8) / $info['playtime_seconds'];
     171                                        }
     172                                        break;
     173
     174                                case GETID3_ASF_Stream_Properties_Object:
     175                                        // Stream Properties Object: (mandatory, one per media stream)
     176                                        // Field Name                   Field Type   Size (bits)
     177                                        // Object ID                    GUID         128             // GUID for stream properties object - GETID3_ASF_Stream_Properties_Object
     178                                        // Object Size                  QWORD        64              // size of stream properties object, including 78 bytes of Stream Properties Object header
     179                                        // Stream Type                  GUID         128             // GETID3_ASF_Audio_Media, GETID3_ASF_Video_Media or GETID3_ASF_Command_Media
     180                                        // Error Correction Type        GUID         128             // GETID3_ASF_Audio_Spread for audio-only streams, GETID3_ASF_No_Error_Correction for other stream types
     181                                        // Time Offset                  QWORD        64              // 100-nanosecond units. typically zero. added to all timestamps of samples in the stream
     182                                        // Type-Specific Data Length    DWORD        32              // number of bytes for Type-Specific Data field
     183                                        // Error Correction Data Length DWORD        32              // number of bytes for Error Correction Data field
     184                                        // Flags                        WORD         16              //
     185                                        // * Stream Number              bits         7 (0x007F)      // number of this stream.  1 <= valid <= 127
     186                                        // * Reserved                   bits         8 (0x7F80)      // reserved - set to zero
     187                                        // * Encrypted Content Flag     bits         1 (0x8000)      // stream contents encrypted if set
     188                                        // Reserved                     DWORD        32              // reserved - set to zero
     189                                        // Type-Specific Data           BYTESTREAM   variable        // type-specific format data, depending on value of Stream Type
     190                                        // Error Correction Data        BYTESTREAM   variable        // error-correction-specific format data, depending on value of Error Correct Type
     191
     192                                        // There is one GETID3_ASF_Stream_Properties_Object for each stream (audio, video) but the
     193                                        // stream number isn't known until halfway through decoding the structure, hence it
     194                                        // it is decoded to a temporary variable and then stuck in the appropriate index later
     195
     196                                        $StreamPropertiesObjectData['offset']             = $NextObjectOffset + $offset;
     197                                        $StreamPropertiesObjectData['objectid']           = $NextObjectGUID;
     198                                        $StreamPropertiesObjectData['objectid_guid']      = $NextObjectGUIDtext;
     199                                        $StreamPropertiesObjectData['objectsize']         = $NextObjectSize;
     200                                        $StreamPropertiesObjectData['stream_type']        = substr($ASFHeaderData, $offset, 16);
     201                                        $offset += 16;
     202                                        $StreamPropertiesObjectData['stream_type_guid']   = $this->BytestringToGUID($StreamPropertiesObjectData['stream_type']);
     203                                        $StreamPropertiesObjectData['error_correct_type'] = substr($ASFHeaderData, $offset, 16);
     204                                        $offset += 16;
     205                                        $StreamPropertiesObjectData['error_correct_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['error_correct_type']);
     206                                        $StreamPropertiesObjectData['time_offset']        = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     207                                        $offset += 8;
     208                                        $StreamPropertiesObjectData['type_data_length']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     209                                        $offset += 4;
     210                                        $StreamPropertiesObjectData['error_data_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     211                                        $offset += 4;
     212                                        $StreamPropertiesObjectData['flags_raw']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     213                                        $offset += 2;
     214                                        $StreamPropertiesObjectStreamNumber               = $StreamPropertiesObjectData['flags_raw'] & 0x007F;
     215                                        $StreamPropertiesObjectData['flags']['encrypted'] = (bool) ($StreamPropertiesObjectData['flags_raw'] & 0x8000);
     216
     217                                        $offset += 4; // reserved - DWORD
     218                                        $StreamPropertiesObjectData['type_specific_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['type_data_length']);
     219                                        $offset += $StreamPropertiesObjectData['type_data_length'];
     220                                        $StreamPropertiesObjectData['error_correct_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['error_data_length']);
     221                                        $offset += $StreamPropertiesObjectData['error_data_length'];
     222
     223                                        switch ($StreamPropertiesObjectData['stream_type']) {
     224
     225                                                case GETID3_ASF_Audio_Media:
     226                                                        $thisfile_audio['dataformat']   = (!empty($thisfile_audio['dataformat'])   ? $thisfile_audio['dataformat']   : 'asf');
     227                                                        $thisfile_audio['bitrate_mode'] = (!empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr');
     228
     229                                                        $audiodata = getid3_riff::parseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16));
     230                                                        unset($audiodata['raw']);
     231                                                        $thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio);
     232                                                        break;
     233
     234                                                case GETID3_ASF_Video_Media:
     235                                                        $thisfile_video['dataformat']   = (!empty($thisfile_video['dataformat'])   ? $thisfile_video['dataformat']   : 'asf');
     236                                                        $thisfile_video['bitrate_mode'] = (!empty($thisfile_video['bitrate_mode']) ? $thisfile_video['bitrate_mode'] : 'cbr');
     237                                                        break;
     238
     239                                                case GETID3_ASF_Command_Media:
     240                                                default:
     241                                                        // do nothing
     242                                                        break;
     243
     244                                        }
     245
     246                                        $thisfile_asf['stream_properties_object'][$StreamPropertiesObjectStreamNumber] = $StreamPropertiesObjectData;
     247                                        unset($StreamPropertiesObjectData); // clear for next stream, if any
     248                                        break;
     249
     250                                case GETID3_ASF_Header_Extension_Object:
     251                                        // Header Extension Object: (mandatory, one only)
     252                                        // Field Name                   Field Type   Size (bits)
     253                                        // Object ID                    GUID         128             // GUID for Header Extension object - GETID3_ASF_Header_Extension_Object
     254                                        // Object Size                  QWORD        64              // size of Header Extension object, including 46 bytes of Header Extension Object header
     255                                        // Reserved Field 1             GUID         128             // hardcoded: GETID3_ASF_Reserved_1
     256                                        // Reserved Field 2             WORD         16              // hardcoded: 0x00000006
     257                                        // Header Extension Data Size   DWORD        32              // in bytes. valid: 0, or > 24. equals object size minus 46
     258                                        // Header Extension Data        BYTESTREAM   variable        // array of zero or more extended header objects
     259
     260                                        // shortcut
     261                                        $thisfile_asf['header_extension_object'] = array();
     262                                        $thisfile_asf_headerextensionobject      = &$thisfile_asf['header_extension_object'];
     263
     264                                        $thisfile_asf_headerextensionobject['offset']              = $NextObjectOffset + $offset;
     265                                        $thisfile_asf_headerextensionobject['objectid']            = $NextObjectGUID;
     266                                        $thisfile_asf_headerextensionobject['objectid_guid']       = $NextObjectGUIDtext;
     267                                        $thisfile_asf_headerextensionobject['objectsize']          = $NextObjectSize;
     268                                        $thisfile_asf_headerextensionobject['reserved_1']          = substr($ASFHeaderData, $offset, 16);
     269                                        $offset += 16;
     270                                        $thisfile_asf_headerextensionobject['reserved_1_guid']     = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']);
     271                                        if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) {
     272                                                $info['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')';
     273                                                //return false;
     274                                                break;
     275                                        }
     276                                        $thisfile_asf_headerextensionobject['reserved_2']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     277                                        $offset += 2;
     278                                        if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) {
     279                                                $info['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"';
     280                                                //return false;
     281                                                break;
     282                                        }
     283                                        $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     284                                        $offset += 4;
     285                                        $thisfile_asf_headerextensionobject['extension_data']      =                              substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']);
     286                                        $unhandled_sections = 0;
     287                                        $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections);
     288                                        if ($unhandled_sections === 0) {
     289                                                unset($thisfile_asf_headerextensionobject['extension_data']);
     290                                        }
     291                                        $offset += $thisfile_asf_headerextensionobject['extension_data_size'];
     292                                        break;
     293
     294                                case GETID3_ASF_Codec_List_Object:
     295                                        // Codec List Object: (optional, one only)
     296                                        // Field Name                   Field Type   Size (bits)
     297                                        // Object ID                    GUID         128             // GUID for Codec List object - GETID3_ASF_Codec_List_Object
     298                                        // Object Size                  QWORD        64              // size of Codec List object, including 44 bytes of Codec List Object header
     299                                        // Reserved                     GUID         128             // hardcoded: 86D15241-311D-11D0-A3A4-00A0C90348F6
     300                                        // Codec Entries Count          DWORD        32              // number of entries in Codec Entries array
     301                                        // Codec Entries                array of:    variable        //
     302                                        // * Type                       WORD         16              // 0x0001 = Video Codec, 0x0002 = Audio Codec, 0xFFFF = Unknown Codec
     303                                        // * Codec Name Length          WORD         16              // number of Unicode characters stored in the Codec Name field
     304                                        // * Codec Name                 WCHAR        variable        // array of Unicode characters - name of codec used to create the content
     305                                        // * Codec Description Length   WORD         16              // number of Unicode characters stored in the Codec Description field
     306                                        // * Codec Description          WCHAR        variable        // array of Unicode characters - description of format used to create the content
     307                                        // * Codec Information Length   WORD         16              // number of Unicode characters stored in the Codec Information field
     308                                        // * Codec Information          BYTESTREAM   variable        // opaque array of information bytes about the codec used to create the content
     309
     310                                        // shortcut
     311                                        $thisfile_asf['codec_list_object'] = array();
     312                                        $thisfile_asf_codeclistobject      = &$thisfile_asf['codec_list_object'];
     313
     314                                        $thisfile_asf_codeclistobject['offset']                    = $NextObjectOffset + $offset;
     315                                        $thisfile_asf_codeclistobject['objectid']                  = $NextObjectGUID;
     316                                        $thisfile_asf_codeclistobject['objectid_guid']             = $NextObjectGUIDtext;
     317                                        $thisfile_asf_codeclistobject['objectsize']                = $NextObjectSize;
     318                                        $thisfile_asf_codeclistobject['reserved']                  = substr($ASFHeaderData, $offset, 16);
     319                                        $offset += 16;
     320                                        $thisfile_asf_codeclistobject['reserved_guid']             = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']);
     321                                        if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) {
     322                                                $info['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}';
     323                                                //return false;
     324                                                break;
     325                                        }
     326                                        $thisfile_asf_codeclistobject['codec_entries_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     327                                        $offset += 4;
     328                                        for ($CodecEntryCounter = 0; $CodecEntryCounter < $thisfile_asf_codeclistobject['codec_entries_count']; $CodecEntryCounter++) {
     329                                                // shortcut
     330                                                $thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter] = array();
     331                                                $thisfile_asf_codeclistobject_codecentries_current = &$thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter];
     332
     333                                                $thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     334                                                $offset += 2;
     335                                                $thisfile_asf_codeclistobject_codecentries_current['type'] = $this->ASFCodecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']);
     336
     337                                                $CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
     338                                                $offset += 2;
     339                                                $thisfile_asf_codeclistobject_codecentries_current['name'] = substr($ASFHeaderData, $offset, $CodecNameLength);
     340                                                $offset += $CodecNameLength;
     341
     342                                                $CodecDescriptionLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
     343                                                $offset += 2;
     344                                                $thisfile_asf_codeclistobject_codecentries_current['description'] = substr($ASFHeaderData, $offset, $CodecDescriptionLength);
     345                                                $offset += $CodecDescriptionLength;
     346
     347                                                $CodecInformationLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     348                                                $offset += 2;
     349                                                $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength);
     350                                                $offset += $CodecInformationLength;
     351
     352                                                if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec
     353
     354                                                        if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) {
     355                                                                $info['warning'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"';
     356                                                        } else {
     357
     358                                                                list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']));
     359                                                                $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']);
     360
     361                                                                if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) {
     362                                                                        $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000);
     363                                                                }
     364                                                                //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) {
     365                                                                if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) {
     366                                                                        //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate'];
     367                                                                        $thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate'];
     368                                                                }
     369
     370                                                                $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency));
     371                                                                switch ($AudioCodecFrequency) {
     372                                                                        case 8:
     373                                                                        case 8000:
     374                                                                                $thisfile_audio['sample_rate'] = 8000;
     375                                                                                break;
     376
     377                                                                        case 11:
     378                                                                        case 11025:
     379                                                                                $thisfile_audio['sample_rate'] = 11025;
     380                                                                                break;
     381
     382                                                                        case 12:
     383                                                                        case 12000:
     384                                                                                $thisfile_audio['sample_rate'] = 12000;
     385                                                                                break;
     386
     387                                                                        case 16:
     388                                                                        case 16000:
     389                                                                                $thisfile_audio['sample_rate'] = 16000;
     390                                                                                break;
     391
     392                                                                        case 22:
     393                                                                        case 22050:
     394                                                                                $thisfile_audio['sample_rate'] = 22050;
     395                                                                                break;
     396
     397                                                                        case 24:
     398                                                                        case 24000:
     399                                                                                $thisfile_audio['sample_rate'] = 24000;
     400                                                                                break;
     401
     402                                                                        case 32:
     403                                                                        case 32000:
     404                                                                                $thisfile_audio['sample_rate'] = 32000;
     405                                                                                break;
     406
     407                                                                        case 44:
     408                                                                        case 441000:
     409                                                                                $thisfile_audio['sample_rate'] = 44100;
     410                                                                                break;
     411
     412                                                                        case 48:
     413                                                                        case 48000:
     414                                                                                $thisfile_audio['sample_rate'] = 48000;
     415                                                                                break;
     416
     417                                                                        default:
     418                                                                                $info['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')';
     419                                                                                break;
     420                                                                }
     421
     422                                                                if (!isset($thisfile_audio['channels'])) {
     423                                                                        if (strstr($AudioCodecChannels, 'stereo')) {
     424                                                                                $thisfile_audio['channels'] = 2;
     425                                                                        } elseif (strstr($AudioCodecChannels, 'mono')) {
     426                                                                                $thisfile_audio['channels'] = 1;
     427                                                                        }
     428                                                                }
     429
     430                                                        }
     431                                                }
     432                                        }
     433                                        break;
     434
     435                                case GETID3_ASF_Script_Command_Object:
     436                                        // Script Command Object: (optional, one only)
     437                                        // Field Name                   Field Type   Size (bits)
     438                                        // Object ID                    GUID         128             // GUID for Script Command object - GETID3_ASF_Script_Command_Object
     439                                        // Object Size                  QWORD        64              // size of Script Command object, including 44 bytes of Script Command Object header
     440                                        // Reserved                     GUID         128             // hardcoded: 4B1ACBE3-100B-11D0-A39B-00A0C90348F6
     441                                        // Commands Count               WORD         16              // number of Commands structures in the Script Commands Objects
     442                                        // Command Types Count          WORD         16              // number of Command Types structures in the Script Commands Objects
     443                                        // Command Types                array of:    variable        //
     444                                        // * Command Type Name Length   WORD         16              // number of Unicode characters for Command Type Name
     445                                        // * Command Type Name          WCHAR        variable        // array of Unicode characters - name of a type of command
     446                                        // Commands                     array of:    variable        //
     447                                        // * Presentation Time          DWORD        32              // presentation time of that command, in milliseconds
     448                                        // * Type Index                 WORD         16              // type of this command, as a zero-based index into the array of Command Types of this object
     449                                        // * Command Name Length        WORD         16              // number of Unicode characters for Command Name
     450                                        // * Command Name               WCHAR        variable        // array of Unicode characters - name of this command
     451
     452                                        // shortcut
     453                                        $thisfile_asf['script_command_object'] = array();
     454                                        $thisfile_asf_scriptcommandobject      = &$thisfile_asf['script_command_object'];
     455
     456                                        $thisfile_asf_scriptcommandobject['offset']               = $NextObjectOffset + $offset;
     457                                        $thisfile_asf_scriptcommandobject['objectid']             = $NextObjectGUID;
     458                                        $thisfile_asf_scriptcommandobject['objectid_guid']        = $NextObjectGUIDtext;
     459                                        $thisfile_asf_scriptcommandobject['objectsize']           = $NextObjectSize;
     460                                        $thisfile_asf_scriptcommandobject['reserved']             = substr($ASFHeaderData, $offset, 16);
     461                                        $offset += 16;
     462                                        $thisfile_asf_scriptcommandobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']);
     463                                        if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) {
     464                                                $info['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}';
     465                                                //return false;
     466                                                break;
     467                                        }
     468                                        $thisfile_asf_scriptcommandobject['commands_count']       = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     469                                        $offset += 2;
     470                                        $thisfile_asf_scriptcommandobject['command_types_count']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     471                                        $offset += 2;
     472                                        for ($CommandTypesCounter = 0; $CommandTypesCounter < $thisfile_asf_scriptcommandobject['command_types_count']; $CommandTypesCounter++) {
     473                                                $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
     474                                                $offset += 2;
     475                                                $thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength);
     476                                                $offset += $CommandTypeNameLength;
     477                                        }
     478                                        for ($CommandsCounter = 0; $CommandsCounter < $thisfile_asf_scriptcommandobject['commands_count']; $CommandsCounter++) {
     479                                                $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['presentation_time']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     480                                                $offset += 4;
     481                                                $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['type_index']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     482                                                $offset += 2;
     483
     484                                                $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
     485                                                $offset += 2;
     486                                                $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength);
     487                                                $offset += $CommandTypeNameLength;
     488                                        }
     489                                        break;
     490
     491                                case GETID3_ASF_Marker_Object:
     492                                        // Marker Object: (optional, one only)
     493                                        // Field Name                   Field Type   Size (bits)
     494                                        // Object ID                    GUID         128             // GUID for Marker object - GETID3_ASF_Marker_Object
     495                                        // Object Size                  QWORD        64              // size of Marker object, including 48 bytes of Marker Object header
     496                                        // Reserved                     GUID         128             // hardcoded: 4CFEDB20-75F6-11CF-9C0F-00A0C90349CB
     497                                        // Markers Count                DWORD        32              // number of Marker structures in Marker Object
     498                                        // Reserved                     WORD         16              // hardcoded: 0x0000
     499                                        // Name Length                  WORD         16              // number of bytes in the Name field
     500                                        // Name                         WCHAR        variable        // name of the Marker Object
     501                                        // Markers                      array of:    variable        //
     502                                        // * Offset                     QWORD        64              // byte offset into Data Object
     503                                        // * Presentation Time          QWORD        64              // in 100-nanosecond units
     504                                        // * Entry Length               WORD         16              // length in bytes of (Send Time + Flags + Marker Description Length + Marker Description + Padding)
     505                                        // * Send Time                  DWORD        32              // in milliseconds
     506                                        // * Flags                      DWORD        32              // hardcoded: 0x00000000
     507                                        // * Marker Description Length  DWORD        32              // number of bytes in Marker Description field
     508                                        // * Marker Description         WCHAR        variable        // array of Unicode characters - description of marker entry
     509                                        // * Padding                    BYTESTREAM   variable        // optional padding bytes
     510
     511                                        // shortcut
     512                                        $thisfile_asf['marker_object'] = array();
     513                                        $thisfile_asf_markerobject     = &$thisfile_asf['marker_object'];
     514
     515                                        $thisfile_asf_markerobject['offset']               = $NextObjectOffset + $offset;
     516                                        $thisfile_asf_markerobject['objectid']             = $NextObjectGUID;
     517                                        $thisfile_asf_markerobject['objectid_guid']        = $NextObjectGUIDtext;
     518                                        $thisfile_asf_markerobject['objectsize']           = $NextObjectSize;
     519                                        $thisfile_asf_markerobject['reserved']             = substr($ASFHeaderData, $offset, 16);
     520                                        $offset += 16;
     521                                        $thisfile_asf_markerobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']);
     522                                        if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) {
     523                                                $info['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}';
     524                                                break;
     525                                        }
     526                                        $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     527                                        $offset += 4;
     528                                        $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     529                                        $offset += 2;
     530                                        if ($thisfile_asf_markerobject['reserved_2'] != 0) {
     531                                                $info['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"';
     532                                                break;
     533                                        }
     534                                        $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     535                                        $offset += 2;
     536                                        $thisfile_asf_markerobject['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['name_length']);
     537                                        $offset += $thisfile_asf_markerobject['name_length'];
     538                                        for ($MarkersCounter = 0; $MarkersCounter < $thisfile_asf_markerobject['markers_count']; $MarkersCounter++) {
     539                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['offset']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     540                                                $offset += 8;
     541                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['presentation_time']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
     542                                                $offset += 8;
     543                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length']              = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     544                                                $offset += 2;
     545                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['send_time']                 = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     546                                                $offset += 4;
     547                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['flags']                     = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     548                                                $offset += 4;
     549                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     550                                                $offset += 4;
     551                                                $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description']        = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']);
     552                                                $offset += $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'];
     553                                                $PaddingLength = $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] - 4 -  4 - 4 - $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'];
     554                                                if ($PaddingLength > 0) {
     555                                                        $thisfile_asf_markerobject['markers'][$MarkersCounter]['padding']               = substr($ASFHeaderData, $offset, $PaddingLength);
     556                                                        $offset += $PaddingLength;
     557                                                }
     558                                        }
     559                                        break;
     560
     561                                case GETID3_ASF_Bitrate_Mutual_Exclusion_Object:
     562                                        // Bitrate Mutual Exclusion Object: (optional)
     563                                        // Field Name                   Field Type   Size (bits)
     564                                        // Object ID                    GUID         128             // GUID for Bitrate Mutual Exclusion object - GETID3_ASF_Bitrate_Mutual_Exclusion_Object
     565                                        // Object Size                  QWORD        64              // size of Bitrate Mutual Exclusion object, including 42 bytes of Bitrate Mutual Exclusion Object header
     566                                        // Exlusion Type                GUID         128             // nature of mutual exclusion relationship. one of: (GETID3_ASF_Mutex_Bitrate, GETID3_ASF_Mutex_Unknown)
     567                                        // Stream Numbers Count         WORD         16              // number of video streams
     568                                        // Stream Numbers               WORD         variable        // array of mutually exclusive video stream numbers. 1 <= valid <= 127
     569
     570                                        // shortcut
     571                                        $thisfile_asf['bitrate_mutual_exclusion_object'] = array();
     572                                        $thisfile_asf_bitratemutualexclusionobject       = &$thisfile_asf['bitrate_mutual_exclusion_object'];
     573
     574                                        $thisfile_asf_bitratemutualexclusionobject['offset']               = $NextObjectOffset + $offset;
     575                                        $thisfile_asf_bitratemutualexclusionobject['objectid']             = $NextObjectGUID;
     576                                        $thisfile_asf_bitratemutualexclusionobject['objectid_guid']        = $NextObjectGUIDtext;
     577                                        $thisfile_asf_bitratemutualexclusionobject['objectsize']           = $NextObjectSize;
     578                                        $thisfile_asf_bitratemutualexclusionobject['reserved']             = substr($ASFHeaderData, $offset, 16);
     579                                        $thisfile_asf_bitratemutualexclusionobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']);
     580                                        $offset += 16;
     581                                        if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) {
     582                                                $info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or  "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}';
     583                                                //return false;
     584                                                break;
     585                                        }
     586                                        $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     587                                        $offset += 2;
     588                                        for ($StreamNumberCounter = 0; $StreamNumberCounter < $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count']; $StreamNumberCounter++) {
     589                                                $thisfile_asf_bitratemutualexclusionobject['stream_numbers'][$StreamNumberCounter] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     590                                                $offset += 2;
     591                                        }
     592                                        break;
     593
     594                                case GETID3_ASF_Error_Correction_Object:
     595                                        // Error Correction Object: (optional, one only)
     596                                        // Field Name                   Field Type   Size (bits)
     597                                        // Object ID                    GUID         128             // GUID for Error Correction object - GETID3_ASF_Error_Correction_Object
     598                                        // Object Size                  QWORD        64              // size of Error Correction object, including 44 bytes of Error Correction Object header
     599                                        // Error Correction Type        GUID         128             // type of error correction. one of: (GETID3_ASF_No_Error_Correction, GETID3_ASF_Audio_Spread)
     600                                        // Error Correction Data Length DWORD        32              // number of bytes in Error Correction Data field
     601                                        // Error Correction Data        BYTESTREAM   variable        // structure depends on value of Error Correction Type field
     602
     603                                        // shortcut
     604                                        $thisfile_asf['error_correction_object'] = array();
     605                                        $thisfile_asf_errorcorrectionobject      = &$thisfile_asf['error_correction_object'];
     606
     607                                        $thisfile_asf_errorcorrectionobject['offset']                = $NextObjectOffset + $offset;
     608                                        $thisfile_asf_errorcorrectionobject['objectid']              = $NextObjectGUID;
     609                                        $thisfile_asf_errorcorrectionobject['objectid_guid']         = $NextObjectGUIDtext;
     610                                        $thisfile_asf_errorcorrectionobject['objectsize']            = $NextObjectSize;
     611                                        $thisfile_asf_errorcorrectionobject['error_correction_type'] = substr($ASFHeaderData, $offset, 16);
     612                                        $offset += 16;
     613                                        $thisfile_asf_errorcorrectionobject['error_correction_guid'] = $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']);
     614                                        $thisfile_asf_errorcorrectionobject['error_correction_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     615                                        $offset += 4;
     616                                        switch ($thisfile_asf_errorcorrectionobject['error_correction_type']) {
     617                                                case GETID3_ASF_No_Error_Correction:
     618                                                        // should be no data, but just in case there is, skip to the end of the field
     619                                                        $offset += $thisfile_asf_errorcorrectionobject['error_correction_data_length'];
     620                                                        break;
     621
     622                                                case GETID3_ASF_Audio_Spread:
     623                                                        // Field Name                   Field Type   Size (bits)
     624                                                        // Span                         BYTE         8               // number of packets over which audio will be spread.
     625                                                        // Virtual Packet Length        WORD         16              // size of largest audio payload found in audio stream
     626                                                        // Virtual Chunk Length         WORD         16              // size of largest audio payload found in audio stream
     627                                                        // Silence Data Length          WORD         16              // number of bytes in Silence Data field
     628                                                        // Silence Data                 BYTESTREAM   variable        // hardcoded: 0x00 * (Silence Data Length) bytes
     629
     630                                                        $thisfile_asf_errorcorrectionobject['span']                  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 1));
     631                                                        $offset += 1;
     632                                                        $thisfile_asf_errorcorrectionobject['virtual_packet_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     633                                                        $offset += 2;
     634                                                        $thisfile_asf_errorcorrectionobject['virtual_chunk_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     635                                                        $offset += 2;
     636                                                        $thisfile_asf_errorcorrectionobject['silence_data_length']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     637                                                        $offset += 2;
     638                                                        $thisfile_asf_errorcorrectionobject['silence_data']          = substr($ASFHeaderData, $offset, $thisfile_asf_errorcorrectionobject['silence_data_length']);
     639                                                        $offset += $thisfile_asf_errorcorrectionobject['silence_data_length'];
     640                                                        break;
     641
     642                                                default:
     643                                                        $info['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or  "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}';
     644                                                        //return false;
     645                                                        break;
     646                                        }
     647
     648                                        break;
     649
     650                                case GETID3_ASF_Content_Description_Object:
     651                                        // Content Description Object: (optional, one only)
     652                                        // Field Name                   Field Type   Size (bits)
     653                                        // Object ID                    GUID         128             // GUID for Content Description object - GETID3_ASF_Content_Description_Object
     654                                        // Object Size                  QWORD        64              // size of Content Description object, including 34 bytes of Content Description Object header
     655                                        // Title Length                 WORD         16              // number of bytes in Title field
     656                                        // Author Length                WORD         16              // number of bytes in Author field
     657                                        // Copyright Length             WORD         16              // number of bytes in Copyright field
     658                                        // Description Length           WORD         16              // number of bytes in Description field
     659                                        // Rating Length                WORD         16              // number of bytes in Rating field
     660                                        // Title                        WCHAR        16              // array of Unicode characters - Title
     661                                        // Author                       WCHAR        16              // array of Unicode characters - Author
     662                                        // Copyright                    WCHAR        16              // array of Unicode characters - Copyright
     663                                        // Description                  WCHAR        16              // array of Unicode characters - Description
     664                                        // Rating                       WCHAR        16              // array of Unicode characters - Rating
     665
     666                                        // shortcut
     667                                        $thisfile_asf['content_description_object'] = array();
     668                                        $thisfile_asf_contentdescriptionobject      = &$thisfile_asf['content_description_object'];
     669
     670                                        $thisfile_asf_contentdescriptionobject['offset']                = $NextObjectOffset + $offset;
     671                                        $thisfile_asf_contentdescriptionobject['objectid']              = $NextObjectGUID;
     672                                        $thisfile_asf_contentdescriptionobject['objectid_guid']         = $NextObjectGUIDtext;
     673                                        $thisfile_asf_contentdescriptionobject['objectsize']            = $NextObjectSize;
     674                                        $thisfile_asf_contentdescriptionobject['title_length']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     675                                        $offset += 2;
     676                                        $thisfile_asf_contentdescriptionobject['author_length']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     677                                        $offset += 2;
     678                                        $thisfile_asf_contentdescriptionobject['copyright_length']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     679                                        $offset += 2;
     680                                        $thisfile_asf_contentdescriptionobject['description_length']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     681                                        $offset += 2;
     682                                        $thisfile_asf_contentdescriptionobject['rating_length']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     683                                        $offset += 2;
     684                                        $thisfile_asf_contentdescriptionobject['title']                 = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['title_length']);
     685                                        $offset += $thisfile_asf_contentdescriptionobject['title_length'];
     686                                        $thisfile_asf_contentdescriptionobject['author']                = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['author_length']);
     687                                        $offset += $thisfile_asf_contentdescriptionobject['author_length'];
     688                                        $thisfile_asf_contentdescriptionobject['copyright']             = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['copyright_length']);
     689                                        $offset += $thisfile_asf_contentdescriptionobject['copyright_length'];
     690                                        $thisfile_asf_contentdescriptionobject['description']           = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['description_length']);
     691                                        $offset += $thisfile_asf_contentdescriptionobject['description_length'];
     692                                        $thisfile_asf_contentdescriptionobject['rating']                = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['rating_length']);
     693                                        $offset += $thisfile_asf_contentdescriptionobject['rating_length'];
     694
     695                                        $ASFcommentKeysToCopy = array('title'=>'title', 'author'=>'artist', 'copyright'=>'copyright', 'description'=>'comment', 'rating'=>'rating');
     696                                        foreach ($ASFcommentKeysToCopy as $keytocopyfrom => $keytocopyto) {
     697                                                if (!empty($thisfile_asf_contentdescriptionobject[$keytocopyfrom])) {
     698                                                        $thisfile_asf_comments[$keytocopyto][] = $this->TrimTerm($thisfile_asf_contentdescriptionobject[$keytocopyfrom]);
     699                                                }
     700                                        }
     701                                        break;
     702
     703                                case GETID3_ASF_Extended_Content_Description_Object:
     704                                        // Extended Content Description Object: (optional, one only)
     705                                        // Field Name                   Field Type   Size (bits)
     706                                        // Object ID                    GUID         128             // GUID for Extended Content Description object - GETID3_ASF_Extended_Content_Description_Object
     707                                        // Object Size                  QWORD        64              // size of ExtendedContent Description object, including 26 bytes of Extended Content Description Object header
     708                                        // Content Descriptors Count    WORD         16              // number of entries in Content Descriptors list
     709                                        // Content Descriptors          array of:    variable        //
     710                                        // * Descriptor Name Length     WORD         16              // size in bytes of Descriptor Name field
     711                                        // * Descriptor Name            WCHAR        variable        // array of Unicode characters - Descriptor Name
     712                                        // * Descriptor Value Data Type WORD         16              // Lookup array:
     713                                                                                                                                                                        // 0x0000 = Unicode String (variable length)
     714                                                                                                                                                                        // 0x0001 = BYTE array     (variable length)
     715                                                                                                                                                                        // 0x0002 = BOOL           (DWORD, 32 bits)
     716                                                                                                                                                                        // 0x0003 = DWORD          (DWORD, 32 bits)
     717                                                                                                                                                                        // 0x0004 = QWORD          (QWORD, 64 bits)
     718                                                                                                                                                                        // 0x0005 = WORD           (WORD,  16 bits)
     719                                        // * Descriptor Value Length    WORD         16              // number of bytes stored in Descriptor Value field
     720                                        // * Descriptor Value           variable     variable        // value for Content Descriptor
     721
     722                                        // shortcut
     723                                        $thisfile_asf['extended_content_description_object'] = array();
     724                                        $thisfile_asf_extendedcontentdescriptionobject       = &$thisfile_asf['extended_content_description_object'];
     725
     726                                        $thisfile_asf_extendedcontentdescriptionobject['offset']                    = $NextObjectOffset + $offset;
     727                                        $thisfile_asf_extendedcontentdescriptionobject['objectid']                  = $NextObjectGUID;
     728                                        $thisfile_asf_extendedcontentdescriptionobject['objectid_guid']             = $NextObjectGUIDtext;
     729                                        $thisfile_asf_extendedcontentdescriptionobject['objectsize']                = $NextObjectSize;
     730                                        $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     731                                        $offset += 2;
     732                                        for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) {
     733                                                // shortcut
     734                                                $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array();
     735                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current                 = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter];
     736
     737                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset']  = $offset + 30;
     738                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     739                                                $offset += 2;
     740                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']         = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']);
     741                                                $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'];
     742                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     743                                                $offset += 2;
     744                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     745                                                $offset += 2;
     746                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']        = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']);
     747                                                $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'];
     748                                                switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) {
     749                                                        case 0x0000: // Unicode string
     750                                                                break;
     751
     752                                                        case 0x0001: // BYTE array
     753                                                                // do nothing
     754                                                                break;
     755
     756                                                        case 0x0002: // BOOL
     757                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = (bool) getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     758                                                                break;
     759
     760                                                        case 0x0003: // DWORD
     761                                                        case 0x0004: // QWORD
     762                                                        case 0x0005: // WORD
     763                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     764                                                                break;
     765
     766                                                        default:
     767                                                                $info['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')';
     768                                                                //return false;
     769                                                                break;
     770                                                }
     771                                                switch ($this->TrimConvert(strtolower($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']))) {
     772
     773                                                        case 'wm/albumartist':
     774                                                        case 'artist':
     775                                                                // Note: not 'artist', that comes from 'author' tag
     776                                                                $thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     777                                                                break;
     778
     779                                                        case 'wm/albumtitle':
     780                                                        case 'album':
     781                                                                $thisfile_asf_comments['album']  = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     782                                                                break;
     783
     784                                                        case 'wm/genre':
     785                                                        case 'genre':
     786                                                                $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     787                                                                break;
     788
     789                                                        case 'wm/partofset':
     790                                                                $thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     791                                                                break;
     792
     793                                                        case 'wm/tracknumber':
     794                                                        case 'tracknumber':
     795                                                                // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character)
     796                                                                $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     797                                                                foreach ($thisfile_asf_comments['track'] as $key => $value) {
     798                                                                        if (preg_match('/^[0-9\x00]+$/', $value)) {
     799                                                                                $thisfile_asf_comments['track'][$key] = intval(str_replace("\x00", '', $value));
     800                                                                        }
     801                                                                }
     802                                                                break;
     803
     804                                                        case 'wm/track':
     805                                                                if (empty($thisfile_asf_comments['track'])) {
     806                                                                        $thisfile_asf_comments['track'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     807                                                                }
     808                                                                break;
     809
     810                                                        case 'wm/year':
     811                                                        case 'year':
     812                                                        case 'date':
     813                                                                $thisfile_asf_comments['year'] = array( $this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     814                                                                break;
     815
     816                                                        case 'wm/lyrics':
     817                                                        case 'lyrics':
     818                                                                $thisfile_asf_comments['lyrics'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     819                                                                break;
     820
     821                                                        case 'isvbr':
     822                                                                if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']) {
     823                                                                        $thisfile_audio['bitrate_mode'] = 'vbr';
     824                                                                        $thisfile_video['bitrate_mode'] = 'vbr';
     825                                                                }
     826                                                                break;
     827
     828                                                        case 'id3':
     829                                                                // id3v2 module might not be loaded
     830                                                                if (class_exists('getid3_id3v2')) {
     831                                                                        $tempfile         = tempnam(GETID3_TEMP_DIR, 'getID3');
     832                                                                        $tempfilehandle   = fopen($tempfile, 'wb');
     833                                                                        $tempThisfileInfo = array('encoding'=>$info['encoding']);
     834                                                                        fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     835                                                                        fclose($tempfilehandle);
     836
     837                                                                        $getid3_temp = new getID3();
     838                                                                        $getid3_temp->openfile($tempfile);
     839                                                                        $getid3_id3v2 = new getid3_id3v2($getid3_temp);
     840                                                                        $getid3_id3v2->Analyze();
     841                                                                        $info['id3v2'] = $getid3_temp->info['id3v2'];
     842                                                                        unset($getid3_temp, $getid3_id3v2);
     843
     844                                                                        unlink($tempfile);
     845                                                                }
     846                                                                break;
     847
     848                                                        case 'wm/encodingtime':
     849                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     850                                                                $thisfile_asf_comments['encoding_time_unix'] = array($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix']);
     851                                                                break;
     852
     853                                                        case 'wm/picture':
     854                                                                $WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     855                                                                foreach ($WMpicture as $key => $value) {
     856                                                                        $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value;
     857                                                                }
     858                                                                unset($WMpicture);
     859/*
     860                                                                $wm_picture_offset = 0;
     861                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1));
     862                                                                $wm_picture_offset += 1;
     863                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type']    = $this->WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']);
     864                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size']    = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4));
     865                                                                $wm_picture_offset += 4;
     866
     867                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = '';
     868                                                                do {
     869                                                                        $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2);
     870                                                                        $wm_picture_offset += 2;
     871                                                                        $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] .= $next_byte_pair;
     872                                                                } while ($next_byte_pair !== "\x00\x00");
     873
     874                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] = '';
     875                                                                do {
     876                                                                        $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2);
     877                                                                        $wm_picture_offset += 2;
     878                                                                        $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] .= $next_byte_pair;
     879                                                                } while ($next_byte_pair !== "\x00\x00");
     880
     881                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['dataoffset'] = $wm_picture_offset;
     882                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset);
     883                                                                unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
     884
     885                                                                $imageinfo = array();
     886                                                                $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = '';
     887                                                                $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo);
     888                                                                unset($imageinfo);
     889                                                                if (!empty($imagechunkcheck)) {
     890                                                                        $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
     891                                                                }
     892                                                                if (!isset($thisfile_asf_comments['picture'])) {
     893                                                                        $thisfile_asf_comments['picture'] = array();
     894                                                                }
     895                                                                $thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']);
     896*/
     897                                                                break;
     898
     899                                                        default:
     900                                                                switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) {
     901                                                                        case 0: // Unicode string
     902                                                                                if (substr($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']), 0, 3) == 'WM/') {
     903                                                                                        $thisfile_asf_comments[str_replace('wm/', '', strtolower($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'])))] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
     904                                                                                }
     905                                                                                break;
     906
     907                                                                        case 1:
     908                                                                                break;
     909                                                                }
     910                                                                break;
     911                                                }
     912
     913                                        }
     914                                        break;
     915
     916                                case GETID3_ASF_Stream_Bitrate_Properties_Object:
     917                                        // Stream Bitrate Properties Object: (optional, one only)
     918                                        // Field Name                   Field Type   Size (bits)
     919                                        // Object ID                    GUID         128             // GUID for Stream Bitrate Properties object - GETID3_ASF_Stream_Bitrate_Properties_Object
     920                                        // Object Size                  QWORD        64              // size of Extended Content Description object, including 26 bytes of Stream Bitrate Properties Object header
     921                                        // Bitrate Records Count        WORD         16              // number of records in Bitrate Records
     922                                        // Bitrate Records              array of:    variable        //
     923                                        // * Flags                      WORD         16              //
     924                                        // * * Stream Number            bits         7  (0x007F)     // number of this stream
     925                                        // * * Reserved                 bits         9  (0xFF80)     // hardcoded: 0
     926                                        // * Average Bitrate            DWORD        32              // in bits per second
     927
     928                                        // shortcut
     929                                        $thisfile_asf['stream_bitrate_properties_object'] = array();
     930                                        $thisfile_asf_streambitratepropertiesobject       = &$thisfile_asf['stream_bitrate_properties_object'];
     931
     932                                        $thisfile_asf_streambitratepropertiesobject['offset']                    = $NextObjectOffset + $offset;
     933                                        $thisfile_asf_streambitratepropertiesobject['objectid']                  = $NextObjectGUID;
     934                                        $thisfile_asf_streambitratepropertiesobject['objectid_guid']             = $NextObjectGUIDtext;
     935                                        $thisfile_asf_streambitratepropertiesobject['objectsize']                = $NextObjectSize;
     936                                        $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']     = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     937                                        $offset += 2;
     938                                        for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) {
     939                                                $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
     940                                                $offset += 2;
     941                                                $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags']['stream_number'] = $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] & 0x007F;
     942                                                $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
     943                                                $offset += 4;
     944                                        }
     945                                        break;
     946
     947                                case GETID3_ASF_Padding_Object:
     948                                        // Padding Object: (optional)
     949                                        // Field Name                   Field Type   Size (bits)
     950                                        // Object ID                    GUID         128             // GUID for Padding object - GETID3_ASF_Padding_Object
     951                                        // Object Size                  QWORD        64              // size of Padding object, including 24 bytes of ASF Padding Object header
     952                                        // Padding Data                 BYTESTREAM   variable        // ignore
     953
     954                                        // shortcut
     955                                        $thisfile_asf['padding_object'] = array();
     956                                        $thisfile_asf_paddingobject     = &$thisfile_asf['padding_object'];
     957
     958                                        $thisfile_asf_paddingobject['offset']                    = $NextObjectOffset + $offset;
     959                                        $thisfile_asf_paddingobject['objectid']                  = $NextObjectGUID;
     960                                        $thisfile_asf_paddingobject['objectid_guid']             = $NextObjectGUIDtext;
     961                                        $thisfile_asf_paddingobject['objectsize']                = $NextObjectSize;
     962                                        $thisfile_asf_paddingobject['padding_length']            = $thisfile_asf_paddingobject['objectsize'] - 16 - 8;
     963                                        $thisfile_asf_paddingobject['padding']                   = substr($ASFHeaderData, $offset, $thisfile_asf_paddingobject['padding_length']);
     964                                        $offset += ($NextObjectSize - 16 - 8);
     965                                        break;
     966
     967                                case GETID3_ASF_Extended_Content_Encryption_Object:
     968                                case GETID3_ASF_Content_Encryption_Object:
     969                                        // WMA DRM - just ignore
     970                                        $offset += ($NextObjectSize - 16 - 8);
     971                                        break;
     972
     973                                default:
     974                                        // Implementations shall ignore any standard or non-standard object that they do not know how to handle.
     975                                        if ($this->GUIDname($NextObjectGUIDtext)) {
     976                                                $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8);
     977                                        } else {
     978                                                $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8);
     979                                        }
     980                                        $offset += ($NextObjectSize - 16 - 8);
     981                                        break;
     982                        }
     983                }
     984                if (isset($thisfile_asf_streambitrateproperties['bitrate_records_count'])) {
     985                        $ASFbitrateAudio = 0;
     986                        $ASFbitrateVideo = 0;
     987                        for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) {
     988                                if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) {
     989                                        switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) {
     990                                                case 1:
     991                                                        $ASFbitrateVideo += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate'];
     992                                                        break;
     993
     994                                                case 2:
     995                                                        $ASFbitrateAudio += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate'];
     996                                                        break;
     997
     998                                                default:
     999                                                        // do nothing
     1000                                                        break;
     1001                                        }
     1002                                }
     1003                        }
     1004                        if ($ASFbitrateAudio > 0) {
     1005                                $thisfile_audio['bitrate'] = $ASFbitrateAudio;
     1006                        }
     1007                        if ($ASFbitrateVideo > 0) {
     1008                                $thisfile_video['bitrate'] = $ASFbitrateVideo;
     1009                        }
     1010                }
     1011                if (isset($thisfile_asf['stream_properties_object']) && is_array($thisfile_asf['stream_properties_object'])) {
     1012
     1013                        $thisfile_audio['bitrate'] = 0;
     1014                        $thisfile_video['bitrate'] = 0;
     1015
     1016                        foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) {
     1017
     1018                                switch ($streamdata['stream_type']) {
     1019                                        case GETID3_ASF_Audio_Media:
     1020                                                // Field Name                   Field Type   Size (bits)
     1021                                                // Codec ID / Format Tag        WORD         16              // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure
     1022                                                // Number of Channels           WORD         16              // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure
     1023                                                // Samples Per Second           DWORD        32              // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure
     1024                                                // Average number of Bytes/sec  DWORD        32              // bytes/sec of audio stream  - defined as nAvgBytesPerSec field of WAVEFORMATEX structure
     1025                                                // Block Alignment              WORD         16              // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure
     1026                                                // Bits per sample              WORD         16              // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure
     1027                                                // Codec Specific Data Size     WORD         16              // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure
     1028                                                // Codec Specific Data          BYTESTREAM   variable        // array of codec-specific data bytes
     1029
     1030                                                // shortcut
     1031                                                $thisfile_asf['audio_media'][$streamnumber] = array();
     1032                                                $thisfile_asf_audiomedia_currentstream      = &$thisfile_asf['audio_media'][$streamnumber];
     1033
     1034                                                $audiomediaoffset = 0;
     1035
     1036                                                $thisfile_asf_audiomedia_currentstream = getid3_riff::parseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16));
     1037                                                $audiomediaoffset += 16;
     1038
     1039                                                $thisfile_audio['lossless'] = false;
     1040                                                switch ($thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']) {
     1041                                                        case 0x0001: // PCM
     1042                                                        case 0x0163: // WMA9 Lossless
     1043                                                                $thisfile_audio['lossless'] = true;
     1044                                                                break;
     1045                                                }
     1046
     1047                                                if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) {
     1048                                                        foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) {
     1049                                                                if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
     1050                                                                        $thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate'];
     1051                                                                        $thisfile_audio['bitrate'] += $dataarray['bitrate'];
     1052                                                                        break;
     1053                                                                }
     1054                                                        }
     1055                                                } else {
     1056                                                        if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) {
     1057                                                                $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8;
     1058                                                        } elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) {
     1059                                                                $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate'];
     1060                                                        }
     1061                                                }
     1062                                                $thisfile_audio['streams'][$streamnumber]                = $thisfile_asf_audiomedia_currentstream;
     1063                                                $thisfile_audio['streams'][$streamnumber]['wformattag']  = $thisfile_asf_audiomedia_currentstream['raw']['wFormatTag'];
     1064                                                $thisfile_audio['streams'][$streamnumber]['lossless']    = $thisfile_audio['lossless'];
     1065                                                $thisfile_audio['streams'][$streamnumber]['bitrate']     = $thisfile_audio['bitrate'];
     1066                                                $thisfile_audio['streams'][$streamnumber]['dataformat']  = 'wma';
     1067                                                unset($thisfile_audio['streams'][$streamnumber]['raw']);
     1068
     1069                                                $thisfile_asf_audiomedia_currentstream['codec_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $audiomediaoffset, 2));
     1070                                                $audiomediaoffset += 2;
     1071                                                $thisfile_asf_audiomedia_currentstream['codec_data']      = substr($streamdata['type_specific_data'], $audiomediaoffset, $thisfile_asf_audiomedia_currentstream['codec_data_size']);
     1072                                                $audiomediaoffset += $thisfile_asf_audiomedia_currentstream['codec_data_size'];
     1073
     1074                                                break;
     1075
     1076                                        case GETID3_ASF_Video_Media:
     1077                                                // Field Name                   Field Type   Size (bits)
     1078                                                // Encoded Image Width          DWORD        32              // width of image in pixels
     1079                                                // Encoded Image Height         DWORD        32              // height of image in pixels
     1080                                                // Reserved Flags               BYTE         8               // hardcoded: 0x02
     1081                                                // Format Data Size             WORD         16              // size of Format Data field in bytes
     1082                                                // Format Data                  array of:    variable        //
     1083                                                // * Format Data Size           DWORD        32              // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure
     1084                                                // * Image Width                LONG         32              // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure
     1085                                                // * Image Height               LONG         32              // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure
     1086                                                // * Reserved                   WORD         16              // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure
     1087                                                // * Bits Per Pixel Count       WORD         16              // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure
     1088                                                // * Compression ID             FOURCC       32              // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure
     1089                                                // * Image Size                 DWORD        32              // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure
     1090                                                // * Horizontal Pixels / Meter  DWORD        32              // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure
     1091                                                // * Vertical Pixels / Meter    DWORD        32              // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure
     1092                                                // * Colors Used Count          DWORD        32              // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure
     1093                                                // * Important Colors Count     DWORD        32              // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure
     1094                                                // * Codec Specific Data        BYTESTREAM   variable        // array of codec-specific data bytes
     1095
     1096                                                // shortcut
     1097                                                $thisfile_asf['video_media'][$streamnumber] = array();
     1098                                                $thisfile_asf_videomedia_currentstream      = &$thisfile_asf['video_media'][$streamnumber];
     1099
     1100                                                $videomediaoffset = 0;
     1101                                                $thisfile_asf_videomedia_currentstream['image_width']                     = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1102                                                $videomediaoffset += 4;
     1103                                                $thisfile_asf_videomedia_currentstream['image_height']                    = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1104                                                $videomediaoffset += 4;
     1105                                                $thisfile_asf_videomedia_currentstream['flags']                           = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 1));
     1106                                                $videomediaoffset += 1;
     1107                                                $thisfile_asf_videomedia_currentstream['format_data_size']                = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
     1108                                                $videomediaoffset += 2;
     1109                                                $thisfile_asf_videomedia_currentstream['format_data']['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1110                                                $videomediaoffset += 4;
     1111                                                $thisfile_asf_videomedia_currentstream['format_data']['image_width']      = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1112                                                $videomediaoffset += 4;
     1113                                                $thisfile_asf_videomedia_currentstream['format_data']['image_height']     = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1114                                                $videomediaoffset += 4;
     1115                                                $thisfile_asf_videomedia_currentstream['format_data']['reserved']         = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
     1116                                                $videomediaoffset += 2;
     1117                                                $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel']   = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
     1118                                                $videomediaoffset += 2;
     1119                                                $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']     = substr($streamdata['type_specific_data'], $videomediaoffset, 4);
     1120                                                $videomediaoffset += 4;
     1121                                                $thisfile_asf_videomedia_currentstream['format_data']['image_size']       = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1122                                                $videomediaoffset += 4;
     1123                                                $thisfile_asf_videomedia_currentstream['format_data']['horizontal_pels']  = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1124                                                $videomediaoffset += 4;
     1125                                                $thisfile_asf_videomedia_currentstream['format_data']['vertical_pels']    = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1126                                                $videomediaoffset += 4;
     1127                                                $thisfile_asf_videomedia_currentstream['format_data']['colors_used']      = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1128                                                $videomediaoffset += 4;
     1129                                                $thisfile_asf_videomedia_currentstream['format_data']['colors_important'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
     1130                                                $videomediaoffset += 4;
     1131                                                $thisfile_asf_videomedia_currentstream['format_data']['codec_data']       = substr($streamdata['type_specific_data'], $videomediaoffset);
     1132
     1133                                                if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) {
     1134                                                        foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) {
     1135                                                                if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
     1136                                                                        $thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate'];
     1137                                                                        $thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate'];
     1138                                                                        $thisfile_video['bitrate'] += $dataarray['bitrate'];
     1139                                                                        break;
     1140                                                                }
     1141                                                        }
     1142                                                }
     1143
     1144                                                $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::fourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']);
     1145
     1146                                                $thisfile_video['streams'][$streamnumber]['fourcc']          = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc'];
     1147                                                $thisfile_video['streams'][$streamnumber]['codec']           = $thisfile_asf_videomedia_currentstream['format_data']['codec'];
     1148                                                $thisfile_video['streams'][$streamnumber]['resolution_x']    = $thisfile_asf_videomedia_currentstream['image_width'];
     1149                                                $thisfile_video['streams'][$streamnumber]['resolution_y']    = $thisfile_asf_videomedia_currentstream['image_height'];
     1150                                                $thisfile_video['streams'][$streamnumber]['bits_per_sample'] = $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel'];
     1151                                                break;
     1152
     1153                                        default:
     1154                                                break;
     1155                                }
     1156                        }
     1157                }
     1158
     1159                while (ftell($this->getid3->fp) < $info['avdataend']) {
     1160                        $NextObjectDataHeader = fread($this->getid3->fp, 24);
     1161                        $offset = 0;
     1162                        $NextObjectGUID = substr($NextObjectDataHeader, 0, 16);
     1163                        $offset += 16;
     1164                        $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID);
     1165                        $NextObjectSize = getid3_lib::LittleEndian2Int(substr($NextObjectDataHeader, $offset, 8));
     1166                        $offset += 8;
     1167
     1168                        switch ($NextObjectGUID) {
     1169                                case GETID3_ASF_Data_Object:
     1170                                        // Data Object: (mandatory, one only)
     1171                                        // Field Name                       Field Type   Size (bits)
     1172                                        // Object ID                        GUID         128             // GUID for Data object - GETID3_ASF_Data_Object
     1173                                        // Object Size                      QWORD        64              // size of Data object, including 50 bytes of Data Object header. may be 0 if FilePropertiesObject.BroadcastFlag == 1
     1174                                        // File ID                          GUID         128             // unique identifier. identical to File ID field in Header Object
     1175                                        // Total Data Packets               QWORD        64              // number of Data Packet entries in Data Object. invalid if FilePropertiesObject.BroadcastFlag == 1
     1176                                        // Reserved                         WORD         16              // hardcoded: 0x0101
     1177
     1178                                        // shortcut
     1179                                        $thisfile_asf['data_object'] = array();
     1180                                        $thisfile_asf_dataobject     = &$thisfile_asf['data_object'];
     1181
     1182                                        $DataObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 50 - 24);
     1183                                        $offset = 24;
     1184
     1185                                        $thisfile_asf_dataobject['objectid']           = $NextObjectGUID;
     1186                                        $thisfile_asf_dataobject['objectid_guid']      = $NextObjectGUIDtext;
     1187                                        $thisfile_asf_dataobject['objectsize']         = $NextObjectSize;
     1188
     1189                                        $thisfile_asf_dataobject['fileid']             = substr($DataObjectData, $offset, 16);
     1190                                        $offset += 16;
     1191                                        $thisfile_asf_dataobject['fileid_guid']        = $this->BytestringToGUID($thisfile_asf_dataobject['fileid']);
     1192                                        $thisfile_asf_dataobject['total_data_packets'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 8));
     1193                                        $offset += 8;
     1194                                        $thisfile_asf_dataobject['reserved']           = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2));
     1195                                        $offset += 2;
     1196                                        if ($thisfile_asf_dataobject['reserved'] != 0x0101) {
     1197                                                $info['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"';
     1198                                                //return false;
     1199                                                break;
     1200                                        }
     1201
     1202                                        // Data Packets                     array of:    variable        //
     1203                                        // * Error Correction Flags         BYTE         8               //
     1204                                        // * * Error Correction Data Length bits         4               // if Error Correction Length Type == 00, size of Error Correction Data in bytes, else hardcoded: 0000
     1205                                        // * * Opaque Data Present          bits         1               //
     1206                                        // * * Error Correction Length Type bits         2               // number of bits for size of the error correction data. hardcoded: 00
     1207                                        // * * Error Correction Present     bits         1               // If set, use Opaque Data Packet structure, else use Payload structure
     1208                                        // * Error Correction Data
     1209
     1210                                        $info['avdataoffset'] = ftell($this->getid3->fp);
     1211                                        fseek($this->getid3->fp, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data
     1212                                        $info['avdataend'] = ftell($this->getid3->fp);
     1213                                        break;
     1214
     1215                                case GETID3_ASF_Simple_Index_Object:
     1216                                        // Simple Index Object: (optional, recommended, one per video stream)
     1217                                        // Field Name                       Field Type   Size (bits)
     1218                                        // Object ID                        GUID         128             // GUID for Simple Index object - GETID3_ASF_Data_Object
     1219                                        // Object Size                      QWORD        64              // size of Simple Index object, including 56 bytes of Simple Index Object header
     1220                                        // File ID                          GUID         128             // unique identifier. may be zero or identical to File ID field in Data Object and Header Object
     1221                                        // Index Entry Time Interval        QWORD        64              // interval between index entries in 100-nanosecond units
     1222                                        // Maximum Packet Count             DWORD        32              // maximum packet count for all index entries
     1223                                        // Index Entries Count              DWORD        32              // number of Index Entries structures
     1224                                        // Index Entries                    array of:    variable        //
     1225                                        // * Packet Number                  DWORD        32              // number of the Data Packet associated with this index entry
     1226                                        // * Packet Count                   WORD         16              // number of Data Packets to sent at this index entry
     1227
     1228                                        // shortcut
     1229                                        $thisfile_asf['simple_index_object'] = array();
     1230                                        $thisfile_asf_simpleindexobject      = &$thisfile_asf['simple_index_object'];
     1231
     1232                                        $SimpleIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 56 - 24);
     1233                                        $offset = 24;
     1234
     1235                                        $thisfile_asf_simpleindexobject['objectid']                  = $NextObjectGUID;
     1236                                        $thisfile_asf_simpleindexobject['objectid_guid']             = $NextObjectGUIDtext;
     1237                                        $thisfile_asf_simpleindexobject['objectsize']                = $NextObjectSize;
     1238
     1239                                        $thisfile_asf_simpleindexobject['fileid']                    =                  substr($SimpleIndexObjectData, $offset, 16);
     1240                                        $offset += 16;
     1241                                        $thisfile_asf_simpleindexobject['fileid_guid']               = $this->BytestringToGUID($thisfile_asf_simpleindexobject['fileid']);
     1242                                        $thisfile_asf_simpleindexobject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 8));
     1243                                        $offset += 8;
     1244                                        $thisfile_asf_simpleindexobject['maximum_packet_count']      = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4));
     1245                                        $offset += 4;
     1246                                        $thisfile_asf_simpleindexobject['index_entries_count']       = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4));
     1247                                        $offset += 4;
     1248
     1249                                        $IndexEntriesData = $SimpleIndexObjectData.fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']);
     1250                                        for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) {
     1251                                                $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4));
     1252                                                $offset += 4;
     1253                                                $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_count']  = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4));
     1254                                                $offset += 2;
     1255                                        }
     1256
     1257                                        break;
     1258
     1259                                case GETID3_ASF_Index_Object:
     1260                                        // 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1)
     1261                                        // Field Name                       Field Type   Size (bits)
     1262                                        // Object ID                        GUID         128             // GUID for the Index Object - GETID3_ASF_Index_Object
     1263                                        // Object Size                      QWORD        64              // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header
     1264                                        // Index Entry Time Interval        DWORD        32              // Specifies the time interval between each index entry in ms.
     1265                                        // Index Specifiers Count           WORD         16              // Specifies the number of Index Specifiers structures in this Index Object.
     1266                                        // Index Blocks Count               DWORD        32              // Specifies the number of Index Blocks structures in this Index Object.
     1267
     1268                                        // Index Entry Time Interval        DWORD        32              // Specifies the time interval between index entries in milliseconds.  This value cannot be 0.
     1269                                        // Index Specifiers Count           WORD         16              // Specifies the number of entries in the Index Specifiers list.  Valid values are 1 and greater.
     1270                                        // Index Specifiers                 array of:    varies          //
     1271                                        // * Stream Number                  WORD         16              // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127.
     1272                                        // * Index Type                     WORD         16              // Specifies Index Type values as follows:
     1273                                                                                                                                                                        //   1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time.
     1274                                                                                                                                                                        //   2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object.
     1275                                                                                                                                                                        //   3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set.
     1276                                                                                                                                                                        //   Nearest Past Cleanpoint is the most common type of index.
     1277                                        // Index Entry Count                DWORD        32              // Specifies the number of Index Entries in the block.
     1278                                        // * Block Positions                QWORD        varies          // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed.
     1279                                        // * Index Entries                  array of:    varies          //
     1280                                        // * * Offsets                      DWORD        varies          // An offset value of 0xffffffff indicates an invalid offset value
     1281
     1282                                        // shortcut
     1283                                        $thisfile_asf['asf_index_object'] = array();
     1284                                        $thisfile_asf_asfindexobject      = &$thisfile_asf['asf_index_object'];
     1285
     1286                                        $ASFIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 34 - 24);
     1287                                        $offset = 24;
     1288
     1289                                        $thisfile_asf_asfindexobject['objectid']                  = $NextObjectGUID;
     1290                                        $thisfile_asf_asfindexobject['objectid_guid']             = $NextObjectGUIDtext;
     1291                                        $thisfile_asf_asfindexobject['objectsize']                = $NextObjectSize;
     1292
     1293                                        $thisfile_asf_asfindexobject['entry_time_interval']       = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
     1294                                        $offset += 4;
     1295                                        $thisfile_asf_asfindexobject['index_specifiers_count']    = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
     1296                                        $offset += 2;
     1297                                        $thisfile_asf_asfindexobject['index_blocks_count']        = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
     1298                                        $offset += 4;
     1299
     1300                                        $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']);
     1301                                        for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
     1302                                                $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
     1303                                                $offset += 2;
     1304                                                $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['stream_number']   = $IndexSpecifierStreamNumber;
     1305                                                $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']      = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
     1306                                                $offset += 2;
     1307                                                $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']);
     1308                                        }
     1309
     1310                                        $ASFIndexObjectData .= fread($this->getid3->fp, 4);
     1311                                        $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
     1312                                        $offset += 4;
     1313
     1314                                        $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']);
     1315                                        for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
     1316                                                $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8));
     1317                                                $offset += 8;
     1318                                        }
     1319
     1320                                        $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']);
     1321                                        for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) {
     1322                                                for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
     1323                                                        $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
     1324                                                        $offset += 4;
     1325                                                }
     1326                                        }
     1327                                        break;
     1328
     1329
     1330                                default:
     1331                                        // Implementations shall ignore any standard or non-standard object that they do not know how to handle.
     1332                                        if ($this->GUIDname($NextObjectGUIDtext)) {
     1333                                                $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8);
     1334                                        } else {
     1335                                                $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($this->getid3->fp) - 16 - 8);
     1336                                        }
     1337                                        fseek($this->getid3->fp, ($NextObjectSize - 16 - 8), SEEK_CUR);
     1338                                        break;
     1339                        }
     1340                }
     1341
     1342                if (isset($thisfile_asf_codeclistobject['codec_entries']) && is_array($thisfile_asf_codeclistobject['codec_entries'])) {
     1343                        foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) {
     1344                                switch ($streamdata['information']) {
     1345                                        case 'WMV1':
     1346                                        case 'WMV2':
     1347                                        case 'WMV3':
     1348                                        case 'MSS1':
     1349                                        case 'MSS2':
     1350                                        case 'WMVA':
     1351                                        case 'WVC1':
     1352                                        case 'WMVP':
     1353                                        case 'WVP2':
     1354                                                $thisfile_video['dataformat'] = 'wmv';
     1355                                                $info['mime_type'] = 'video/x-ms-wmv';
     1356                                                break;
     1357
     1358                                        case 'MP42':
     1359                                        case 'MP43':
     1360                                        case 'MP4S':
     1361                                        case 'mp4s':
     1362                                                $thisfile_video['dataformat'] = 'asf';
     1363                                                $info['mime_type'] = 'video/x-ms-asf';
     1364                                                break;
     1365
     1366                                        default:
     1367                                                switch ($streamdata['type_raw']) {
     1368                                                        case 1:
     1369                                                                if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) {
     1370                                                                        $thisfile_video['dataformat'] = 'wmv';
     1371                                                                        if ($info['mime_type'] == 'video/x-ms-asf') {
     1372                                                                                $info['mime_type'] = 'video/x-ms-wmv';
     1373                                                                        }
     1374                                                                }
     1375                                                                break;
     1376
     1377                                                        case 2:
     1378                                                                if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) {
     1379                                                                        $thisfile_audio['dataformat'] = 'wma';
     1380                                                                        if ($info['mime_type'] == 'video/x-ms-asf') {
     1381                                                                                $info['mime_type'] = 'audio/x-ms-wma';
     1382                                                                        }
     1383                                                                }
     1384                                                                break;
     1385
     1386                                                }
     1387                                                break;
     1388                                }
     1389                        }
     1390                }
     1391
     1392                switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') {
     1393                        case 'MPEG Layer-3':
     1394                                $thisfile_audio['dataformat'] = 'mp3';
     1395                                break;
     1396
     1397                        default:
     1398                                break;
     1399                }
     1400
     1401                if (isset($thisfile_asf_codeclistobject['codec_entries'])) {
     1402                        foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) {
     1403                                switch ($streamdata['type_raw']) {
     1404
     1405                                        case 1: // video
     1406                                                $thisfile_video['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']);
     1407                                                break;
     1408
     1409                                        case 2: // audio
     1410                                                $thisfile_audio['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']);
     1411
     1412                                                // AH 2003-10-01
     1413                                                $thisfile_audio['encoder_options'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][0]['description']);
     1414
     1415                                                $thisfile_audio['codec']   = $thisfile_audio['encoder'];
     1416                                                break;
     1417
     1418                                        default:
     1419                                                $info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw'];
     1420                                                break;
     1421
     1422                                }
     1423                        }
     1424                }
     1425
     1426                if (isset($info['audio'])) {
     1427                        $thisfile_audio['lossless']           = (isset($thisfile_audio['lossless'])           ? $thisfile_audio['lossless']           : false);
     1428                        $thisfile_audio['dataformat']         = (!empty($thisfile_audio['dataformat'])        ? $thisfile_audio['dataformat']         : 'asf');
     1429                }
     1430                if (!empty($thisfile_video['dataformat'])) {
     1431                        $thisfile_video['lossless']           = (isset($thisfile_audio['lossless'])           ? $thisfile_audio['lossless']           : false);
     1432                        $thisfile_video['pixel_aspect_ratio'] = (isset($thisfile_audio['pixel_aspect_ratio']) ? $thisfile_audio['pixel_aspect_ratio'] : (float) 1);
     1433                        $thisfile_video['dataformat']         = (!empty($thisfile_video['dataformat'])        ? $thisfile_video['dataformat']         : 'asf');
     1434                }
     1435                if (!empty($thisfile_video['streams'])) {
     1436                        $thisfile_video['streams']['resolution_x'] = 0;
     1437                        $thisfile_video['streams']['resolution_y'] = 0;
     1438                        foreach ($thisfile_video['streams'] as $key => $valuearray) {
     1439                                if (($valuearray['resolution_x'] > $thisfile_video['streams']['resolution_x']) || ($valuearray['resolution_y'] > $thisfile_video['streams']['resolution_y'])) {
     1440                                        $thisfile_video['resolution_x'] = $valuearray['resolution_x'];
     1441                                        $thisfile_video['resolution_y'] = $valuearray['resolution_y'];
     1442                                }
     1443                        }
     1444                }
     1445                $info['bitrate'] = (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0);
     1446
     1447                if ((!isset($info['playtime_seconds']) || ($info['playtime_seconds'] <= 0)) && ($info['bitrate'] > 0)) {
     1448                        $info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8);
     1449                }
     1450
     1451                return true;
     1452        }
     1453
     1454        public static function ASFCodecListObjectTypeLookup($CodecListType) {
     1455                static $ASFCodecListObjectTypeLookup = array();
     1456                if (empty($ASFCodecListObjectTypeLookup)) {
     1457                        $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec';
     1458                        $ASFCodecListObjectTypeLookup[0x0002] = 'Audio Codec';
     1459                        $ASFCodecListObjectTypeLookup[0xFFFF] = 'Unknown Codec';
     1460                }
     1461
     1462                return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type');
     1463        }
     1464
     1465        public static function KnownGUIDs() {
     1466                static $GUIDarray = array(
     1467                        'GETID3_ASF_Extended_Stream_Properties_Object'   => '14E6A5CB-C672-4332-8399-A96952065B5A',
     1468                        'GETID3_ASF_Padding_Object'                      => '1806D474-CADF-4509-A4BA-9AABCB96AAE8',
     1469                        'GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8',
     1470                        'GETID3_ASF_Script_Command_Object'               => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6',
     1471                        'GETID3_ASF_No_Error_Correction'                 => '20FB5700-5B55-11CF-A8FD-00805F5C442B',
     1472                        'GETID3_ASF_Content_Branding_Object'             => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E',
     1473                        'GETID3_ASF_Content_Encryption_Object'           => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E',
     1474                        'GETID3_ASF_Digital_Signature_Object'            => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E',
     1475                        'GETID3_ASF_Extended_Content_Encryption_Object'  => '298AE614-2622-4C17-B935-DAE07EE9289C',
     1476                        'GETID3_ASF_Simple_Index_Object'                 => '33000890-E5B1-11CF-89F4-00A0C90349CB',
     1477                        'GETID3_ASF_Degradable_JPEG_Media'               => '35907DE0-E415-11CF-A917-00805F5C442B',
     1478                        'GETID3_ASF_Payload_Extension_System_Timecode'   => '399595EC-8667-4E2D-8FDB-98814CE76C1E',
     1479                        'GETID3_ASF_Binary_Media'                        => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343',
     1480                        'GETID3_ASF_Timecode_Index_Object'               => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C',
     1481                        'GETID3_ASF_Metadata_Library_Object'             => '44231C94-9498-49D1-A141-1D134E457054',
     1482                        'GETID3_ASF_Reserved_3'                          => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6',
     1483                        'GETID3_ASF_Reserved_4'                          => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB',
     1484                        'GETID3_ASF_Command_Media'                       => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6',
     1485                        'GETID3_ASF_Header_Extension_Object'             => '5FBF03B5-A92E-11CF-8EE3-00C00C205365',
     1486                        'GETID3_ASF_Media_Object_Index_Parameters_Obj'   => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7',
     1487                        'GETID3_ASF_Header_Object'                       => '75B22630-668E-11CF-A6D9-00AA0062CE6C',
     1488                        'GETID3_ASF_Content_Description_Object'          => '75B22633-668E-11CF-A6D9-00AA0062CE6C',
     1489                        'GETID3_ASF_Error_Correction_Object'             => '75B22635-668E-11CF-A6D9-00AA0062CE6C',
     1490                        'GETID3_ASF_Data_Object'                         => '75B22636-668E-11CF-A6D9-00AA0062CE6C',
     1491                        'GETID3_ASF_Web_Stream_Media_Subtype'            => '776257D4-C627-41CB-8F81-7AC7FF1C40CC',
     1492                        'GETID3_ASF_Stream_Bitrate_Properties_Object'    => '7BF875CE-468D-11D1-8D82-006097C9A2B2',
     1493                        'GETID3_ASF_Language_List_Object'                => '7C4346A9-EFE0-4BFC-B229-393EDE415C85',
     1494                        'GETID3_ASF_Codec_List_Object'                   => '86D15240-311D-11D0-A3A4-00A0C90348F6',
     1495                        'GETID3_ASF_Reserved_2'                          => '86D15241-311D-11D0-A3A4-00A0C90348F6',
     1496                        'GETID3_ASF_File_Properties_Object'              => '8CABDCA1-A947-11CF-8EE4-00C00C205365',
     1497                        'GETID3_ASF_File_Transfer_Media'                 => '91BD222C-F21C-497A-8B6D-5AA86BFC0185',
     1498                        'GETID3_ASF_Old_RTP_Extension_Data'              => '96800C63-4C94-11D1-837B-0080C7A37F95',
     1499                        'GETID3_ASF_Advanced_Mutual_Exclusion_Object'    => 'A08649CF-4775-4670-8A16-6E35357566CD',
     1500                        'GETID3_ASF_Bandwidth_Sharing_Object'            => 'A69609E6-517B-11D2-B6AF-00C04FD908E9',
     1501                        'GETID3_ASF_Reserved_1'                          => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365',
     1502                        'GETID3_ASF_Bandwidth_Sharing_Exclusive'         => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9',
     1503                        'GETID3_ASF_Bandwidth_Sharing_Partial'           => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9',
     1504                        'GETID3_ASF_JFIF_Media'                          => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B',
     1505                        'GETID3_ASF_Stream_Properties_Object'            => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365',
     1506                        'GETID3_ASF_Video_Media'                         => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B',
     1507                        'GETID3_ASF_Audio_Spread'                        => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220',
     1508                        'GETID3_ASF_Metadata_Object'                     => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA',
     1509                        'GETID3_ASF_Payload_Ext_Syst_Sample_Duration'    => 'C6BD9450-867F-4907-83A3-C77921B733AD',
     1510                        'GETID3_ASF_Group_Mutual_Exclusion_Object'       => 'D1465A40-5A79-4338-B71B-E36B8FD6C249',
     1511                        'GETID3_ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850',
     1512                        'GETID3_ASF_Stream_Prioritization_Object'        => 'D4FED15B-88D3-454F-81F0-ED5C45999E24',
     1513                        'GETID3_ASF_Payload_Ext_System_Content_Type'     => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC',
     1514                        'GETID3_ASF_Old_File_Properties_Object'          => 'D6E229D0-35DA-11D1-9034-00A0C90349BE',
     1515                        'GETID3_ASF_Old_ASF_Header_Object'               => 'D6E229D1-35DA-11D1-9034-00A0C90349BE',
     1516                        'GETID3_ASF_Old_ASF_Data_Object'                 => 'D6E229D2-35DA-11D1-9034-00A0C90349BE',
     1517                        'GETID3_ASF_Index_Object'                        => 'D6E229D3-35DA-11D1-9034-00A0C90349BE',
     1518                        'GETID3_ASF_Old_Stream_Properties_Object'        => 'D6E229D4-35DA-11D1-9034-00A0C90349BE',
     1519                        'GETID3_ASF_Old_Content_Description_Object'      => 'D6E229D5-35DA-11D1-9034-00A0C90349BE',
     1520                        'GETID3_ASF_Old_Script_Command_Object'           => 'D6E229D6-35DA-11D1-9034-00A0C90349BE',
     1521                        'GETID3_ASF_Old_Marker_Object'                   => 'D6E229D7-35DA-11D1-9034-00A0C90349BE',
     1522                        'GETID3_ASF_Old_Component_Download_Object'       => 'D6E229D8-35DA-11D1-9034-00A0C90349BE',
     1523                        'GETID3_ASF_Old_Stream_Group_Object'             => 'D6E229D9-35DA-11D1-9034-00A0C90349BE',
     1524                        'GETID3_ASF_Old_Scalable_Object'                 => 'D6E229DA-35DA-11D1-9034-00A0C90349BE',
     1525                        'GETID3_ASF_Old_Prioritization_Object'           => 'D6E229DB-35DA-11D1-9034-00A0C90349BE',
     1526                        'GETID3_ASF_Bitrate_Mutual_Exclusion_Object'     => 'D6E229DC-35DA-11D1-9034-00A0C90349BE',
     1527                        'GETID3_ASF_Old_Inter_Media_Dependency_Object'   => 'D6E229DD-35DA-11D1-9034-00A0C90349BE',
     1528                        'GETID3_ASF_Old_Rating_Object'                   => 'D6E229DE-35DA-11D1-9034-00A0C90349BE',
     1529                        'GETID3_ASF_Index_Parameters_Object'             => 'D6E229DF-35DA-11D1-9034-00A0C90349BE',
     1530                        'GETID3_ASF_Old_Color_Table_Object'              => 'D6E229E0-35DA-11D1-9034-00A0C90349BE',
     1531                        'GETID3_ASF_Old_Language_List_Object'            => 'D6E229E1-35DA-11D1-9034-00A0C90349BE',
     1532                        'GETID3_ASF_Old_Audio_Media'                     => 'D6E229E2-35DA-11D1-9034-00A0C90349BE',
     1533                        'GETID3_ASF_Old_Video_Media'                     => 'D6E229E3-35DA-11D1-9034-00A0C90349BE',
     1534                        'GETID3_ASF_Old_Image_Media'                     => 'D6E229E4-35DA-11D1-9034-00A0C90349BE',
     1535                        'GETID3_ASF_Old_Timecode_Media'                  => 'D6E229E5-35DA-11D1-9034-00A0C90349BE',
     1536                        'GETID3_ASF_Old_Text_Media'                      => 'D6E229E6-35DA-11D1-9034-00A0C90349BE',
     1537                        'GETID3_ASF_Old_MIDI_Media'                      => 'D6E229E7-35DA-11D1-9034-00A0C90349BE',
     1538                        'GETID3_ASF_Old_Command_Media'                   => 'D6E229E8-35DA-11D1-9034-00A0C90349BE',
     1539                        'GETID3_ASF_Old_No_Error_Concealment'            => 'D6E229EA-35DA-11D1-9034-00A0C90349BE',
     1540                        'GETID3_ASF_Old_Scrambled_Audio'                 => 'D6E229EB-35DA-11D1-9034-00A0C90349BE',
     1541                        'GETID3_ASF_Old_No_Color_Table'                  => 'D6E229EC-35DA-11D1-9034-00A0C90349BE',
     1542                        'GETID3_ASF_Old_SMPTE_Time'                      => 'D6E229ED-35DA-11D1-9034-00A0C90349BE',
     1543                        'GETID3_ASF_Old_ASCII_Text'                      => 'D6E229EE-35DA-11D1-9034-00A0C90349BE',
     1544                        'GETID3_ASF_Old_Unicode_Text'                    => 'D6E229EF-35DA-11D1-9034-00A0C90349BE',
     1545                        'GETID3_ASF_Old_HTML_Text'                       => 'D6E229F0-35DA-11D1-9034-00A0C90349BE',
     1546                        'GETID3_ASF_Old_URL_Command'                     => 'D6E229F1-35DA-11D1-9034-00A0C90349BE',
     1547                        'GETID3_ASF_Old_Filename_Command'                => 'D6E229F2-35DA-11D1-9034-00A0C90349BE',
     1548                        'GETID3_ASF_Old_ACM_Codec'                       => 'D6E229F3-35DA-11D1-9034-00A0C90349BE',
     1549                        'GETID3_ASF_Old_VCM_Codec'                       => 'D6E229F4-35DA-11D1-9034-00A0C90349BE',
     1550                        'GETID3_ASF_Old_QuickTime_Codec'                 => 'D6E229F5-35DA-11D1-9034-00A0C90349BE',
     1551                        'GETID3_ASF_Old_DirectShow_Transform_Filter'     => 'D6E229F6-35DA-11D1-9034-00A0C90349BE',
     1552                        'GETID3_ASF_Old_DirectShow_Rendering_Filter'     => 'D6E229F7-35DA-11D1-9034-00A0C90349BE',
     1553                        'GETID3_ASF_Old_No_Enhancement'                  => 'D6E229F8-35DA-11D1-9034-00A0C90349BE',
     1554                        'GETID3_ASF_Old_Unknown_Enhancement_Type'        => 'D6E229F9-35DA-11D1-9034-00A0C90349BE',
     1555                        'GETID3_ASF_Old_Temporal_Enhancement'            => 'D6E229FA-35DA-11D1-9034-00A0C90349BE',
     1556                        'GETID3_ASF_Old_Spatial_Enhancement'             => 'D6E229FB-35DA-11D1-9034-00A0C90349BE',
     1557                        'GETID3_ASF_Old_Quality_Enhancement'             => 'D6E229FC-35DA-11D1-9034-00A0C90349BE',
     1558                        'GETID3_ASF_Old_Number_of_Channels_Enhancement'  => 'D6E229FD-35DA-11D1-9034-00A0C90349BE',
     1559                        'GETID3_ASF_Old_Frequency_Response_Enhancement'  => 'D6E229FE-35DA-11D1-9034-00A0C90349BE',
     1560                        'GETID3_ASF_Old_Media_Object'                    => 'D6E229FF-35DA-11D1-9034-00A0C90349BE',
     1561                        'GETID3_ASF_Mutex_Language'                      => 'D6E22A00-35DA-11D1-9034-00A0C90349BE',
     1562                        'GETID3_ASF_Mutex_Bitrate'                       => 'D6E22A01-35DA-11D1-9034-00A0C90349BE',
     1563                        'GETID3_ASF_Mutex_Unknown'                       => 'D6E22A02-35DA-11D1-9034-00A0C90349BE',
     1564                        'GETID3_ASF_Old_ASF_Placeholder_Object'          => 'D6E22A0E-35DA-11D1-9034-00A0C90349BE',
     1565                        'GETID3_ASF_Old_Data_Unit_Extension_Object'      => 'D6E22A0F-35DA-11D1-9034-00A0C90349BE',
     1566                        'GETID3_ASF_Web_Stream_Format'                   => 'DA1E6B13-8359-4050-B398-388E965BF00C',
     1567                        'GETID3_ASF_Payload_Ext_System_File_Name'        => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B',
     1568                        'GETID3_ASF_Marker_Object'                       => 'F487CD01-A951-11CF-8EE6-00C00C205365',
     1569                        'GETID3_ASF_Timecode_Index_Parameters_Object'    => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24',
     1570                        'GETID3_ASF_Audio_Media'                         => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B',
     1571                        'GETID3_ASF_Media_Object_Index_Object'           => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C',
     1572                        'GETID3_ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE',
     1573                        'GETID3_ASF_Index_Placeholder_Object'            => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html
     1574                        'GETID3_ASF_Compatibility_Object'                => '26F18B5D-4584-47EC-9F5F-0E651F0452C9', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html
     1575                );
     1576                return $GUIDarray;
     1577        }
     1578
     1579        public static function GUIDname($GUIDstring) {
     1580                static $GUIDarray = array();
     1581                if (empty($GUIDarray)) {
     1582                        $GUIDarray = self::KnownGUIDs();
     1583                }
     1584                return array_search($GUIDstring, $GUIDarray);
     1585        }
     1586
     1587        public static function ASFIndexObjectIndexTypeLookup($id) {
     1588                static $ASFIndexObjectIndexTypeLookup = array();
     1589                if (empty($ASFIndexObjectIndexTypeLookup)) {
     1590                        $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet';
     1591                        $ASFIndexObjectIndexTypeLookup[2] = 'Nearest Past Media Object';
     1592                        $ASFIndexObjectIndexTypeLookup[3] = 'Nearest Past Cleanpoint';
     1593                }
     1594                return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid');
     1595        }
     1596
     1597        public static function GUIDtoBytestring($GUIDstring) {
     1598                // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way:
     1599                // first 4 bytes are in little-endian order
     1600                // next 2 bytes are appended in little-endian order
     1601                // next 2 bytes are appended in little-endian order
     1602                // next 2 bytes are appended in big-endian order
     1603                // next 6 bytes are appended in big-endian order
     1604
     1605                // AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string:
     1606                // $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
     1607
     1608                $hexbytecharstring  = chr(hexdec(substr($GUIDstring,  6, 2)));
     1609                $hexbytecharstring .= chr(hexdec(substr($GUIDstring,  4, 2)));
     1610                $hexbytecharstring .= chr(hexdec(substr($GUIDstring,  2, 2)));
     1611                $hexbytecharstring .= chr(hexdec(substr($GUIDstring,  0, 2)));
     1612
     1613                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2)));
     1614                $hexbytecharstring .= chr(hexdec(substr($GUIDstring,  9, 2)));
     1615
     1616                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2)));
     1617                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2)));
     1618
     1619                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2)));
     1620                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2)));
     1621
     1622                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2)));
     1623                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2)));
     1624                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2)));
     1625                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2)));
     1626                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2)));
     1627                $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2)));
     1628
     1629                return $hexbytecharstring;
     1630        }
     1631
     1632        public static function BytestringToGUID($Bytestring) {
     1633                $GUIDstring  = str_pad(dechex(ord($Bytestring{3})),  2, '0', STR_PAD_LEFT);
     1634                $GUIDstring .= str_pad(dechex(ord($Bytestring{2})),  2, '0', STR_PAD_LEFT);
     1635                $GUIDstring .= str_pad(dechex(ord($Bytestring{1})),  2, '0', STR_PAD_LEFT);
     1636                $GUIDstring .= str_pad(dechex(ord($Bytestring{0})),  2, '0', STR_PAD_LEFT);
     1637                $GUIDstring .= '-';
     1638                $GUIDstring .= str_pad(dechex(ord($Bytestring{5})),  2, '0', STR_PAD_LEFT);
     1639                $GUIDstring .= str_pad(dechex(ord($Bytestring{4})),  2, '0', STR_PAD_LEFT);
     1640                $GUIDstring .= '-';
     1641                $GUIDstring .= str_pad(dechex(ord($Bytestring{7})),  2, '0', STR_PAD_LEFT);
     1642                $GUIDstring .= str_pad(dechex(ord($Bytestring{6})),  2, '0', STR_PAD_LEFT);
     1643                $GUIDstring .= '-';
     1644                $GUIDstring .= str_pad(dechex(ord($Bytestring{8})),  2, '0', STR_PAD_LEFT);
     1645                $GUIDstring .= str_pad(dechex(ord($Bytestring{9})),  2, '0', STR_PAD_LEFT);
     1646                $GUIDstring .= '-';
     1647                $GUIDstring .= str_pad(dechex(ord($Bytestring{10})), 2, '0', STR_PAD_LEFT);
     1648                $GUIDstring .= str_pad(dechex(ord($Bytestring{11})), 2, '0', STR_PAD_LEFT);
     1649                $GUIDstring .= str_pad(dechex(ord($Bytestring{12})), 2, '0', STR_PAD_LEFT);
     1650                $GUIDstring .= str_pad(dechex(ord($Bytestring{13})), 2, '0', STR_PAD_LEFT);
     1651                $GUIDstring .= str_pad(dechex(ord($Bytestring{14})), 2, '0', STR_PAD_LEFT);
     1652                $GUIDstring .= str_pad(dechex(ord($Bytestring{15})), 2, '0', STR_PAD_LEFT);
     1653
     1654                return strtoupper($GUIDstring);
     1655        }
     1656
     1657        public static function FILETIMEtoUNIXtime($FILETIME, $round=true) {
     1658                // FILETIME is a 64-bit unsigned integer representing
     1659                // the number of 100-nanosecond intervals since January 1, 1601
     1660                // UNIX timestamp is number of seconds since January 1, 1970
     1661                // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
     1662                if ($round) {
     1663                        return intval(round(($FILETIME - 116444736000000000) / 10000000));
     1664                }
     1665                return ($FILETIME - 116444736000000000) / 10000000;
     1666        }
     1667
     1668        public static function WMpictureTypeLookup($WMpictureType) {
     1669                static $WMpictureTypeLookup = array();
     1670                if (empty($WMpictureTypeLookup)) {
     1671                        $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover');
     1672                        $WMpictureTypeLookup[0x04] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Back Cover');
     1673                        $WMpictureTypeLookup[0x00] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'User Defined');
     1674                        $WMpictureTypeLookup[0x05] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Leaflet Page');
     1675                        $WMpictureTypeLookup[0x06] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Media Label');
     1676                        $WMpictureTypeLookup[0x07] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lead Artist');
     1677                        $WMpictureTypeLookup[0x08] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Artist');
     1678                        $WMpictureTypeLookup[0x09] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Conductor');
     1679                        $WMpictureTypeLookup[0x0A] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band');
     1680                        $WMpictureTypeLookup[0x0B] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Composer');
     1681                        $WMpictureTypeLookup[0x0C] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lyricist');
     1682                        $WMpictureTypeLookup[0x0D] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Recording Location');
     1683                        $WMpictureTypeLookup[0x0E] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Recording');
     1684                        $WMpictureTypeLookup[0x0F] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Performance');
     1685                        $WMpictureTypeLookup[0x10] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Video Screen Capture');
     1686                        $WMpictureTypeLookup[0x12] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Illustration');
     1687                        $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype');
     1688                        $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype');
     1689                }
     1690                return (isset($WMpictureTypeLookup[$WMpictureType]) ? $WMpictureTypeLookup[$WMpictureType] : '');
     1691        }
     1692
     1693        public function ASF_HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) {
     1694                // http://msdn.microsoft.com/en-us/library/bb643323.aspx
     1695
     1696                $offset = 0;
     1697                $objectOffset = 0;
     1698                $HeaderExtensionObjectParsed = array();
     1699                while ($objectOffset < strlen($asf_header_extension_object_data)) {
     1700                        $offset = $objectOffset;
     1701                        $thisObject = array();
     1702
     1703                        $thisObject['guid']                              =                              substr($asf_header_extension_object_data, $offset, 16);
     1704                        $offset += 16;
     1705                        $thisObject['guid_text'] = $this->BytestringToGUID($thisObject['guid']);
     1706                        $thisObject['guid_name'] = $this->GUIDname($thisObject['guid_text']);
     1707
     1708                        $thisObject['size']                              = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
     1709                        $offset += 8;
     1710                        if ($thisObject['size'] <= 0) {
     1711                                break;
     1712                        }
     1713
     1714                        switch ($thisObject['guid']) {
     1715                                case GETID3_ASF_Extended_Stream_Properties_Object:
     1716                                        $thisObject['start_time']                        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
     1717                                        $offset += 8;
     1718                                        $thisObject['start_time_unix']                   = $this->FILETIMEtoUNIXtime($thisObject['start_time']);
     1719
     1720                                        $thisObject['end_time']                          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
     1721                                        $offset += 8;
     1722                                        $thisObject['end_time_unix']                     = $this->FILETIMEtoUNIXtime($thisObject['end_time']);
     1723
     1724                                        $thisObject['data_bitrate']                      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1725                                        $offset += 4;
     1726
     1727                                        $thisObject['buffer_size']                       = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1728                                        $offset += 4;
     1729
     1730                                        $thisObject['initial_buffer_fullness']           = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1731                                        $offset += 4;
     1732
     1733                                        $thisObject['alternate_data_bitrate']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1734                                        $offset += 4;
     1735
     1736                                        $thisObject['alternate_buffer_size']             = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1737                                        $offset += 4;
     1738
     1739                                        $thisObject['alternate_initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1740                                        $offset += 4;
     1741
     1742                                        $thisObject['maximum_object_size']               = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1743                                        $offset += 4;
     1744
     1745                                        $thisObject['flags_raw']                         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1746                                        $offset += 4;
     1747                                        $thisObject['flags']['reliable']                = (bool) $thisObject['flags_raw'] & 0x00000001;
     1748                                        $thisObject['flags']['seekable']                = (bool) $thisObject['flags_raw'] & 0x00000002;
     1749                                        $thisObject['flags']['no_cleanpoints']          = (bool) $thisObject['flags_raw'] & 0x00000004;
     1750                                        $thisObject['flags']['resend_live_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000008;
     1751
     1752                                        $thisObject['stream_number']                     = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1753                                        $offset += 2;
     1754
     1755                                        $thisObject['stream_language_id_index']          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1756                                        $offset += 2;
     1757
     1758                                        $thisObject['average_time_per_frame']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1759                                        $offset += 4;
     1760
     1761                                        $thisObject['stream_name_count']                 = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1762                                        $offset += 2;
     1763
     1764                                        $thisObject['payload_extension_system_count']    = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1765                                        $offset += 2;
     1766
     1767                                        for ($i = 0; $i < $thisObject['stream_name_count']; $i++) {
     1768                                                $streamName = array();
     1769
     1770                                                $streamName['language_id_index']             = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1771                                                $offset += 2;
     1772
     1773                                                $streamName['stream_name_length']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1774                                                $offset += 2;
     1775
     1776                                                $streamName['stream_name']                   = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  $streamName['stream_name_length']));
     1777                                                $offset += $streamName['stream_name_length'];
     1778
     1779                                                $thisObject['stream_names'][$i] = $streamName;
     1780                                        }
     1781
     1782                                        for ($i = 0; $i < $thisObject['payload_extension_system_count']; $i++) {
     1783                                                $payloadExtensionSystem = array();
     1784
     1785                                                $payloadExtensionSystem['extension_system_id']   =                              substr($asf_header_extension_object_data, $offset, 16);
     1786                                                $offset += 16;
     1787                                                $payloadExtensionSystem['extension_system_id_text'] = $this->BytestringToGUID($payloadExtensionSystem['extension_system_id']);
     1788
     1789                                                $payloadExtensionSystem['extension_system_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1790                                                $offset += 2;
     1791                                                if ($payloadExtensionSystem['extension_system_size'] <= 0) {
     1792                                                        break 2;
     1793                                                }
     1794
     1795                                                $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1796                                                $offset += 4;
     1797
     1798                                                $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  $payloadExtensionSystem['extension_system_info_length']));
     1799                                                $offset += $payloadExtensionSystem['extension_system_info_length'];
     1800
     1801                                                $thisObject['payload_extension_systems'][$i] = $payloadExtensionSystem;
     1802                                        }
     1803
     1804                                        break;
     1805
     1806                                case GETID3_ASF_Padding_Object:
     1807                                        // padding, skip it
     1808                                        break;
     1809
     1810                                case GETID3_ASF_Metadata_Object:
     1811                                        $thisObject['description_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1812                                        $offset += 2;
     1813
     1814                                        for ($i = 0; $i < $thisObject['description_record_counts']; $i++) {
     1815                                                $descriptionRecord = array();
     1816
     1817                                                $descriptionRecord['reserved_1']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2)); // must be zero
     1818                                                $offset += 2;
     1819
     1820                                                $descriptionRecord['stream_number']      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1821                                                $offset += 2;
     1822
     1823                                                $descriptionRecord['name_length']        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1824                                                $offset += 2;
     1825
     1826                                                $descriptionRecord['data_type']          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1827                                                $offset += 2;
     1828                                                $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']);
     1829
     1830                                                $descriptionRecord['data_length']        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1831                                                $offset += 4;
     1832
     1833                                                $descriptionRecord['name']               =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['name_length']);
     1834                                                $offset += $descriptionRecord['name_length'];
     1835
     1836                                                $descriptionRecord['data']               =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['data_length']);
     1837                                                $offset += $descriptionRecord['data_length'];
     1838                                                switch ($descriptionRecord['data_type']) {
     1839                                                        case 0x0000: // Unicode string
     1840                                                                break;
     1841
     1842                                                        case 0x0001: // BYTE array
     1843                                                                // do nothing
     1844                                                                break;
     1845
     1846                                                        case 0x0002: // BOOL
     1847                                                                $descriptionRecord['data'] = (bool) getid3_lib::LittleEndian2Int($descriptionRecord['data']);
     1848                                                                break;
     1849
     1850                                                        case 0x0003: // DWORD
     1851                                                        case 0x0004: // QWORD
     1852                                                        case 0x0005: // WORD
     1853                                                                $descriptionRecord['data'] = getid3_lib::LittleEndian2Int($descriptionRecord['data']);
     1854                                                                break;
     1855
     1856                                                        case 0x0006: // GUID
     1857                                                                $descriptionRecord['data_text'] = $this->BytestringToGUID($descriptionRecord['data']);
     1858                                                                break;
     1859                                                }
     1860
     1861                                                $thisObject['description_record'][$i] = $descriptionRecord;
     1862                                        }
     1863                                        break;
     1864
     1865                                case GETID3_ASF_Language_List_Object:
     1866                                        $thisObject['language_id_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1867                                        $offset += 2;
     1868
     1869                                        for ($i = 0; $i < $thisObject['language_id_record_counts']; $i++) {
     1870                                                $languageIDrecord = array();
     1871
     1872                                                $languageIDrecord['language_id_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  1));
     1873                                                $offset += 1;
     1874
     1875                                                $languageIDrecord['language_id']                =                              substr($asf_header_extension_object_data, $offset,  $languageIDrecord['language_id_length']);
     1876                                                $offset += $languageIDrecord['language_id_length'];
     1877
     1878                                                $thisObject['language_id_record'][$i] = $languageIDrecord;
     1879                                        }
     1880                                        break;
     1881
     1882                                case GETID3_ASF_Metadata_Library_Object:
     1883                                        $thisObject['description_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1884                                        $offset += 2;
     1885
     1886                                        for ($i = 0; $i < $thisObject['description_records_count']; $i++) {
     1887                                                $descriptionRecord = array();
     1888
     1889                                                $descriptionRecord['language_list_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1890                                                $offset += 2;
     1891
     1892                                                $descriptionRecord['stream_number']       = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1893                                                $offset += 2;
     1894
     1895                                                $descriptionRecord['name_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1896                                                $offset += 2;
     1897
     1898                                                $descriptionRecord['data_type']           = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
     1899                                                $offset += 2;
     1900                                                $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']);
     1901
     1902                                                $descriptionRecord['data_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
     1903                                                $offset += 4;
     1904
     1905                                                $descriptionRecord['name']                =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['name_length']);
     1906                                                $offset += $descriptionRecord['name_length'];
     1907
     1908                                                $descriptionRecord['data']                =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['data_length']);
     1909                                                $offset += $descriptionRecord['data_length'];
     1910
     1911                                                if (preg_match('#^WM/Picture$#', str_replace("\x00", '', trim($descriptionRecord['name'])))) {
     1912                                                        $WMpicture = $this->ASF_WMpicture($descriptionRecord['data']);
     1913                                                        foreach ($WMpicture as $key => $value) {
     1914                                                                $descriptionRecord['data'] = $WMpicture;
     1915                                                        }
     1916                                                        unset($WMpicture);
     1917                                                }
     1918
     1919                                                $thisObject['description_record'][$i] = $descriptionRecord;
     1920                                        }
     1921                                        break;
     1922
     1923                                default:
     1924                                        $unhandled_sections++;
     1925                                        if ($this->GUIDname($thisObject['guid_text'])) {
     1926                                                $this->getid3->info['warning'][] = 'unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8);
     1927                                        } else {
     1928                                                $this->getid3->info['warning'][] = 'unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8);
     1929                                        }
     1930                                        break;
     1931                        }
     1932                        $HeaderExtensionObjectParsed[] = $thisObject;
     1933
     1934                        $objectOffset += $thisObject['size'];
     1935                }
     1936                return $HeaderExtensionObjectParsed;
     1937        }
     1938
     1939
     1940        public static function ASFmetadataLibraryObjectDataTypeLookup($id) {
     1941                static $ASFmetadataLibraryObjectDataTypeLookup = array(
     1942                        0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters
     1943                        0x0001 => 'BYTE array',     // The type of the data is implementation-specific
     1944                        0x0002 => 'BOOL',           // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values
     1945                        0x0003 => 'DWORD',          // The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer
     1946                        0x0004 => 'QWORD',          // The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer
     1947                        0x0005 => 'WORD',           // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer
     1948                        0x0006 => 'GUID',           // The data is 16 bytes long and should be interpreted as a 128-bit GUID
     1949                );
     1950                return (isset($ASFmetadataLibraryObjectDataTypeLookup[$id]) ? $ASFmetadataLibraryObjectDataTypeLookup[$id] : 'invalid');
     1951        }
     1952
     1953        public function ASF_WMpicture(&$data) {
     1954                //typedef struct _WMPicture{
     1955                //  LPWSTR  pwszMIMEType;
     1956                //  BYTE  bPictureType;
     1957                //  LPWSTR  pwszDescription;
     1958                //  DWORD  dwDataLen;
     1959                //  BYTE*  pbData;
     1960                //} WM_PICTURE;
     1961
     1962                $WMpicture = array();
     1963
     1964                $offset = 0;
     1965                $WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1));
     1966                $offset += 1;
     1967                $WMpicture['image_type']    = $this->WMpictureTypeLookup($WMpicture['image_type_id']);
     1968                $WMpicture['image_size']    = getid3_lib::LittleEndian2Int(substr($data, $offset, 4));
     1969                $offset += 4;
     1970
     1971                $WMpicture['image_mime'] = '';
     1972                do {
     1973                        $next_byte_pair = substr($data, $offset, 2);
     1974                        $offset += 2;
     1975                        $WMpicture['image_mime'] .= $next_byte_pair;
     1976                } while ($next_byte_pair !== "\x00\x00");
     1977
     1978                $WMpicture['image_description'] = '';
     1979                do {
     1980                        $next_byte_pair = substr($data, $offset, 2);
     1981                        $offset += 2;
     1982                        $WMpicture['image_description'] .= $next_byte_pair;
     1983                } while ($next_byte_pair !== "\x00\x00");
     1984
     1985                $WMpicture['dataoffset'] = $offset;
     1986                $WMpicture['data'] = substr($data, $offset);
     1987
     1988                $imageinfo = array();
     1989                $WMpicture['image_mime'] = '';
     1990                $imagechunkcheck = getid3_lib::GetDataImageSize($WMpicture['data'], $imageinfo);
     1991                unset($imageinfo);
     1992                if (!empty($imagechunkcheck)) {
     1993                        $WMpicture['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
     1994                }
     1995                if (!isset($this->getid3->info['asf']['comments']['picture'])) {
     1996                        $this->getid3->info['asf']['comments']['picture'] = array();
     1997                }
     1998                $this->getid3->info['asf']['comments']['picture'][] = array('data'=>$WMpicture['data'], 'image_mime'=>$WMpicture['image_mime']);
     1999
     2000                return $WMpicture;
     2001        }
     2002
     2003
     2004        // Remove terminator 00 00 and convert UTF-16LE to Latin-1
     2005        public static function TrimConvert($string) {
     2006                return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', self::TrimTerm($string)), ' ');
     2007        }
     2008
     2009
     2010        // Remove terminator 00 00
     2011        public static function TrimTerm($string) {
     2012                // remove terminator, only if present (it should be, but...)
     2013                if (substr($string, -2) === "\x00\x00") {
     2014                        $string = substr($string, 0, -2);
     2015                }
     2016                return $string;
     2017        }
     2018
     2019}
  • new file wp-includes/ID3/module.audio-video.flv.php

    diff --git wp-includes/ID3/module.audio-video.flv.php wp-includes/ID3/module.audio-video.flv.php
    new file mode 100644
    index 0000000..f9c4cf3
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6//                                                             //
     7//  FLV module by Seth Kaufman <sethØwhirl-i-gig*com>          //
     8//                                                             //
     9//  * version 0.1 (26 June 2005)                               //
     10//                                                             //
     11//                                                             //
     12//  * version 0.1.1 (15 July 2005)                             //
     13//  minor modifications by James Heinrich <info@getid3.org>    //
     14//                                                             //
     15//  * version 0.2 (22 February 2006)                           //
     16//  Support for On2 VP6 codec and meta information             //
     17//    by Steve Webster <steve.websterØfeaturecreep*com>        //
     18//                                                             //
     19//  * version 0.3 (15 June 2006)                               //
     20//  Modified to not read entire file into memory               //
     21//    by James Heinrich <info@getid3.org>                      //
     22//                                                             //
     23//  * version 0.4 (07 December 2007)                           //
     24//  Bugfixes for incorrectly parsed FLV dimensions             //
     25//    and incorrect parsing of onMetaTag                       //
     26//    by Evgeny Moysevich <moysevichØgmail*com>                //
     27//                                                             //
     28//  * version 0.5 (21 May 2009)                                //
     29//  Fixed parsing of audio tags and added additional codec     //
     30//    details. The duration is now read from onMetaTag (if     //
     31//    exists), rather than parsing whole file                  //
     32//    by Nigel Barnes <ngbarnesØhotmail*com>                   //
     33//                                                             //
     34//  * version 0.6 (24 May 2009)                                //
     35//  Better parsing of files with h264 video                    //
     36//    by Evgeny Moysevich <moysevichØgmail*com>                //
     37//                                                             //
     38//  * version 0.6.1 (30 May 2011)                              //
     39//    prevent infinite loops in expGolombUe()                  //
     40//                                                             //
     41/////////////////////////////////////////////////////////////////
     42//                                                             //
     43// module.audio-video.flv.php                                  //
     44// module for analyzing Shockwave Flash Video files            //
     45// dependencies: NONE                                          //
     46//                                                            ///
     47/////////////////////////////////////////////////////////////////
     48
     49define('GETID3_FLV_TAG_AUDIO',          8);
     50define('GETID3_FLV_TAG_VIDEO',          9);
     51define('GETID3_FLV_TAG_META',          18);
     52
     53define('GETID3_FLV_VIDEO_H263',         2);
     54define('GETID3_FLV_VIDEO_SCREEN',       3);
     55define('GETID3_FLV_VIDEO_VP6FLV',       4);
     56define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
     57define('GETID3_FLV_VIDEO_SCREENV2',     6);
     58define('GETID3_FLV_VIDEO_H264',         7);
     59
     60define('H264_AVC_SEQUENCE_HEADER',          0);
     61define('H264_PROFILE_BASELINE',            66);
     62define('H264_PROFILE_MAIN',                77);
     63define('H264_PROFILE_EXTENDED',            88);
     64define('H264_PROFILE_HIGH',               100);
     65define('H264_PROFILE_HIGH10',             110);
     66define('H264_PROFILE_HIGH422',            122);
     67define('H264_PROFILE_HIGH444',            144);
     68define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
     69
     70class getid3_flv extends getid3_handler
     71{
     72        public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
     73
     74        public function Analyze() {
     75                $info = &$this->getid3->info;
     76
     77                fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     78
     79                $FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
     80                $FLVheader = fread($this->getid3->fp, 5);
     81
     82                $info['fileformat'] = 'flv';
     83                $info['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
     84                $info['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
     85                $TypeFlags                          = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
     86
     87                $magic = 'FLV';
     88                if ($info['flv']['header']['signature'] != $magic) {
     89                        $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
     90                        unset($info['flv']);
     91                        unset($info['fileformat']);
     92                        return false;
     93                }
     94
     95                $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
     96                $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
     97
     98                $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4));
     99                $FLVheaderFrameLength = 9;
     100                if ($FrameSizeDataLength > $FLVheaderFrameLength) {
     101                        fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
     102                }
     103                $Duration = 0;
     104                $found_video = false;
     105                $found_audio = false;
     106                $found_meta  = false;
     107                $found_valid_meta_playtime = false;
     108                $tagParseCount = 0;
     109                $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
     110                $flv_framecount = &$info['flv']['framecount'];
     111                while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime))  {
     112                        $ThisTagHeader = fread($this->getid3->fp, 16);
     113
     114                        $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
     115                        $TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
     116                        $DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
     117                        $Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
     118                        $LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
     119                        $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength;
     120                        if ($Timestamp > $Duration) {
     121                                $Duration = $Timestamp;
     122                        }
     123
     124                        $flv_framecount['total']++;
     125                        switch ($TagType) {
     126                                case GETID3_FLV_TAG_AUDIO:
     127                                        $flv_framecount['audio']++;
     128                                        if (!$found_audio) {
     129                                                $found_audio = true;
     130                                                $info['flv']['audio']['audioFormat']     = ($LastHeaderByte >> 4) & 0x0F;
     131                                                $info['flv']['audio']['audioRate']       = ($LastHeaderByte >> 2) & 0x03;
     132                                                $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
     133                                                $info['flv']['audio']['audioType']       =  $LastHeaderByte       & 0x01;
     134                                        }
     135                                        break;
     136
     137                                case GETID3_FLV_TAG_VIDEO:
     138                                        $flv_framecount['video']++;
     139                                        if (!$found_video) {
     140                                                $found_video = true;
     141                                                $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
     142
     143                                                $FLVvideoHeader = fread($this->getid3->fp, 11);
     144
     145                                                if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
     146                                                        // this code block contributed by: moysevichØgmail*com
     147
     148                                                        $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
     149                                                        if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
     150                                                                //      read AVCDecoderConfigurationRecord
     151                                                                $configurationVersion       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  4, 1));
     152                                                                $AVCProfileIndication       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  5, 1));
     153                                                                $profile_compatibility      = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  6, 1));
     154                                                                $lengthSizeMinusOne         = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  7, 1));
     155                                                                $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  8, 1));
     156
     157                                                                if (($numOfSequenceParameterSets & 0x1F) != 0) {
     158                                                                        //      there is at least one SequenceParameterSet
     159                                                                        //      read size of the first SequenceParameterSet
     160                                                                        //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
     161                                                                        $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
     162                                                                        //      read the first SequenceParameterSet
     163                                                                        $sps = fread($this->getid3->fp, $spsSize);
     164                                                                        if (strlen($sps) == $spsSize) { //      make sure that whole SequenceParameterSet was red
     165                                                                                $spsReader = new AVCSequenceParameterSetReader($sps);
     166                                                                                $spsReader->readData();
     167                                                                                $info['video']['resolution_x'] = $spsReader->getWidth();
     168                                                                                $info['video']['resolution_y'] = $spsReader->getHeight();
     169                                                                        }
     170                                                                }
     171                                                        }
     172                                                        // end: moysevichØgmail*com
     173
     174                                                } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
     175
     176                                                        $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
     177                                                        $PictureSizeType = $PictureSizeType & 0x0007;
     178                                                        $info['flv']['header']['videoSizeType'] = $PictureSizeType;
     179                                                        switch ($PictureSizeType) {
     180                                                                case 0:
     181                                                                        //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
     182                                                                        //$PictureSizeEnc <<= 1;
     183                                                                        //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
     184                                                                        //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
     185                                                                        //$PictureSizeEnc <<= 1;
     186                                                                        //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
     187
     188                                                                        $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
     189                                                                        $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
     190                                                                        $PictureSizeEnc['x'] >>= 7;
     191                                                                        $PictureSizeEnc['y'] >>= 7;
     192                                                                        $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
     193                                                                        $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
     194                                                                        break;
     195
     196                                                                case 1:
     197                                                                        $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
     198                                                                        $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
     199                                                                        $PictureSizeEnc['x'] >>= 7;
     200                                                                        $PictureSizeEnc['y'] >>= 7;
     201                                                                        $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
     202                                                                        $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
     203                                                                        break;
     204
     205                                                                case 2:
     206                                                                        $info['video']['resolution_x'] = 352;
     207                                                                        $info['video']['resolution_y'] = 288;
     208                                                                        break;
     209
     210                                                                case 3:
     211                                                                        $info['video']['resolution_x'] = 176;
     212                                                                        $info['video']['resolution_y'] = 144;
     213                                                                        break;
     214
     215                                                                case 4:
     216                                                                        $info['video']['resolution_x'] = 128;
     217                                                                        $info['video']['resolution_y'] = 96;
     218                                                                        break;
     219
     220                                                                case 5:
     221                                                                        $info['video']['resolution_x'] = 320;
     222                                                                        $info['video']['resolution_y'] = 240;
     223                                                                        break;
     224
     225                                                                case 6:
     226                                                                        $info['video']['resolution_x'] = 160;
     227                                                                        $info['video']['resolution_y'] = 120;
     228                                                                        break;
     229
     230                                                                default:
     231                                                                        $info['video']['resolution_x'] = 0;
     232                                                                        $info['video']['resolution_y'] = 0;
     233                                                                        break;
     234
     235                                                        }
     236                                                }
     237                                                $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
     238                                        }
     239                                        break;
     240
     241                                // Meta tag
     242                                case GETID3_FLV_TAG_META:
     243                                        if (!$found_meta) {
     244                                                $found_meta = true;
     245                                                fseek($this->getid3->fp, -1, SEEK_CUR);
     246                                                $datachunk = fread($this->getid3->fp, $DataLength);
     247                                                $AMFstream = new AMFStream($datachunk);
     248                                                $reader = new AMFReader($AMFstream);
     249                                                $eventName = $reader->readData();
     250                                                $info['flv']['meta'][$eventName] = $reader->readData();
     251                                                unset($reader);
     252
     253                                                $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
     254                                                foreach ($copykeys as $sourcekey => $destkey) {
     255                                                        if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
     256                                                                switch ($sourcekey) {
     257                                                                        case 'width':
     258                                                                        case 'height':
     259                                                                                $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
     260                                                                                break;
     261                                                                        case 'audiodatarate':
     262                                                                                $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
     263                                                                                break;
     264                                                                        case 'videodatarate':
     265                                                                        case 'frame_rate':
     266                                                                        default:
     267                                                                                $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
     268                                                                                break;
     269                                                                }
     270                                                        }
     271                                                }
     272                                                if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
     273                                                        $found_valid_meta_playtime = true;
     274                                                }
     275                                        }
     276                                        break;
     277
     278                                default:
     279                                        // noop
     280                                        break;
     281                        }
     282                        fseek($this->getid3->fp, $NextOffset, SEEK_SET);
     283                }
     284
     285                $info['playtime_seconds'] = $Duration / 1000;
     286                if ($info['playtime_seconds'] > 0) {
     287                        $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
     288                }
     289
     290                if ($info['flv']['header']['hasAudio']) {
     291                        $info['audio']['codec']           =   $this->FLVaudioFormat($info['flv']['audio']['audioFormat']);
     292                        $info['audio']['sample_rate']     =     $this->FLVaudioRate($info['flv']['audio']['audioRate']);
     293                        $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']);
     294
     295                        $info['audio']['channels']   =  $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
     296                        $info['audio']['lossless']   = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
     297                        $info['audio']['dataformat'] = 'flv';
     298                }
     299                if (!empty($info['flv']['header']['hasVideo'])) {
     300                        $info['video']['codec']      = $this->FLVvideoCodec($info['flv']['video']['videoCodec']);
     301                        $info['video']['dataformat'] = 'flv';
     302                        $info['video']['lossless']   = false;
     303                }
     304
     305                // Set information from meta
     306                if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
     307                        $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
     308                        $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
     309                }
     310                if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
     311                        $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']);
     312                }
     313                if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
     314                        $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']);
     315                }
     316                return true;
     317        }
     318
     319
     320        public function FLVaudioFormat($id) {
     321                $FLVaudioFormat = array(
     322                        0  => 'Linear PCM, platform endian',
     323                        1  => 'ADPCM',
     324                        2  => 'mp3',
     325                        3  => 'Linear PCM, little endian',
     326                        4  => 'Nellymoser 16kHz mono',
     327                        5  => 'Nellymoser 8kHz mono',
     328                        6  => 'Nellymoser',
     329                        7  => 'G.711A-law logarithmic PCM',
     330                        8  => 'G.711 mu-law logarithmic PCM',
     331                        9  => 'reserved',
     332                        10 => 'AAC',
     333                        11 => false, // unknown?
     334                        12 => false, // unknown?
     335                        13 => false, // unknown?
     336                        14 => 'mp3 8kHz',
     337                        15 => 'Device-specific sound',
     338                );
     339                return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false);
     340        }
     341
     342        public function FLVaudioRate($id) {
     343                $FLVaudioRate = array(
     344                        0 =>  5500,
     345                        1 => 11025,
     346                        2 => 22050,
     347                        3 => 44100,
     348                );
     349                return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false);
     350        }
     351
     352        public function FLVaudioBitDepth($id) {
     353                $FLVaudioBitDepth = array(
     354                        0 =>  8,
     355                        1 => 16,
     356                );
     357                return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false);
     358        }
     359
     360        public function FLVvideoCodec($id) {
     361                $FLVvideoCodec = array(
     362                        GETID3_FLV_VIDEO_H263         => 'Sorenson H.263',
     363                        GETID3_FLV_VIDEO_SCREEN       => 'Screen video',
     364                        GETID3_FLV_VIDEO_VP6FLV       => 'On2 VP6',
     365                        GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
     366                        GETID3_FLV_VIDEO_SCREENV2     => 'Screen video v2',
     367                        GETID3_FLV_VIDEO_H264         => 'Sorenson H.264',
     368                );
     369                return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false);
     370        }
     371}
     372
     373class AMFStream {
     374        public $bytes;
     375        public $pos;
     376
     377        public function AMFStream(&$bytes) {
     378                $this->bytes =& $bytes;
     379                $this->pos = 0;
     380        }
     381
     382        public function readByte() {
     383                return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
     384        }
     385
     386        public function readInt() {
     387                return ($this->readByte() << 8) + $this->readByte();
     388        }
     389
     390        public function readLong() {
     391                return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
     392        }
     393
     394        public function readDouble() {
     395                return getid3_lib::BigEndian2Float($this->read(8));
     396        }
     397
     398        public function readUTF() {
     399                $length = $this->readInt();
     400                return $this->read($length);
     401        }
     402
     403        public function readLongUTF() {
     404                $length = $this->readLong();
     405                return $this->read($length);
     406        }
     407
     408        public function read($length) {
     409                $val = substr($this->bytes, $this->pos, $length);
     410                $this->pos += $length;
     411                return $val;
     412        }
     413
     414        public function peekByte() {
     415                $pos = $this->pos;
     416                $val = $this->readByte();
     417                $this->pos = $pos;
     418                return $val;
     419        }
     420
     421        public function peekInt() {
     422                $pos = $this->pos;
     423                $val = $this->readInt();
     424                $this->pos = $pos;
     425                return $val;
     426        }
     427
     428        public function peekLong() {
     429                $pos = $this->pos;
     430                $val = $this->readLong();
     431                $this->pos = $pos;
     432                return $val;
     433        }
     434
     435        public function peekDouble() {
     436                $pos = $this->pos;
     437                $val = $this->readDouble();
     438                $this->pos = $pos;
     439                return $val;
     440        }
     441
     442        public function peekUTF() {
     443                $pos = $this->pos;
     444                $val = $this->readUTF();
     445                $this->pos = $pos;
     446                return $val;
     447        }
     448
     449        public function peekLongUTF() {
     450                $pos = $this->pos;
     451                $val = $this->readLongUTF();
     452                $this->pos = $pos;
     453                return $val;
     454        }
     455}
     456
     457class AMFReader {
     458        public $stream;
     459
     460        public function AMFReader(&$stream) {
     461                $this->stream =& $stream;
     462        }
     463
     464        public function readData() {
     465                $value = null;
     466
     467                $type = $this->stream->readByte();
     468                switch ($type) {
     469
     470                        // Double
     471                        case 0:
     472                                $value = $this->readDouble();
     473                        break;
     474
     475                        // Boolean
     476                        case 1:
     477                                $value = $this->readBoolean();
     478                                break;
     479
     480                        // String
     481                        case 2:
     482                                $value = $this->readString();
     483                                break;
     484
     485                        // Object
     486                        case 3:
     487                                $value = $this->readObject();
     488                                break;
     489
     490                        // null
     491                        case 6:
     492                                return null;
     493                                break;
     494
     495                        // Mixed array
     496                        case 8:
     497                                $value = $this->readMixedArray();
     498                                break;
     499
     500                        // Array
     501                        case 10:
     502                                $value = $this->readArray();
     503                                break;
     504
     505                        // Date
     506                        case 11:
     507                                $value = $this->readDate();
     508                                break;
     509
     510                        // Long string
     511                        case 13:
     512                                $value = $this->readLongString();
     513                                break;
     514
     515                        // XML (handled as string)
     516                        case 15:
     517                                $value = $this->readXML();
     518                                break;
     519
     520                        // Typed object (handled as object)
     521                        case 16:
     522                                $value = $this->readTypedObject();
     523                                break;
     524
     525                        // Long string
     526                        default:
     527                                $value = '(unknown or unsupported data type)';
     528                        break;
     529                }
     530
     531                return $value;
     532        }
     533
     534        public function readDouble() {
     535                return $this->stream->readDouble();
     536        }
     537
     538        public function readBoolean() {
     539                return $this->stream->readByte() == 1;
     540        }
     541
     542        public function readString() {
     543                return $this->stream->readUTF();
     544        }
     545
     546        public function readObject() {
     547                // Get highest numerical index - ignored
     548//              $highestIndex = $this->stream->readLong();
     549
     550                $data = array();
     551
     552                while ($key = $this->stream->readUTF()) {
     553                        $data[$key] = $this->readData();
     554                }
     555                // Mixed array record ends with empty string (0x00 0x00) and 0x09
     556                if (($key == '') && ($this->stream->peekByte() == 0x09)) {
     557                        // Consume byte
     558                        $this->stream->readByte();
     559                }
     560                return $data;
     561        }
     562
     563        public function readMixedArray() {
     564                // Get highest numerical index - ignored
     565                $highestIndex = $this->stream->readLong();
     566
     567                $data = array();
     568
     569                while ($key = $this->stream->readUTF()) {
     570                        if (is_numeric($key)) {
     571                                $key = (float) $key;
     572                        }
     573                        $data[$key] = $this->readData();
     574                }
     575                // Mixed array record ends with empty string (0x00 0x00) and 0x09
     576                if (($key == '') && ($this->stream->peekByte() == 0x09)) {
     577                        // Consume byte
     578                        $this->stream->readByte();
     579                }
     580
     581                return $data;
     582        }
     583
     584        public function readArray() {
     585                $length = $this->stream->readLong();
     586                $data = array();
     587
     588                for ($i = 0; $i < $length; $i++) {
     589                        $data[] = $this->readData();
     590                }
     591                return $data;
     592        }
     593
     594        public function readDate() {
     595                $timestamp = $this->stream->readDouble();
     596                $timezone = $this->stream->readInt();
     597                return $timestamp;
     598        }
     599
     600        public function readLongString() {
     601                return $this->stream->readLongUTF();
     602        }
     603
     604        public function readXML() {
     605                return $this->stream->readLongUTF();
     606        }
     607
     608        public function readTypedObject() {
     609                $className = $this->stream->readUTF();
     610                return $this->readObject();
     611        }
     612}
     613
     614class AVCSequenceParameterSetReader {
     615        public $sps;
     616        public $start = 0;
     617        public $currentBytes = 0;
     618        public $currentBits = 0;
     619        public $width;
     620        public $height;
     621
     622        public function AVCSequenceParameterSetReader($sps) {
     623                $this->sps = $sps;
     624        }
     625
     626        public function readData() {
     627                $this->skipBits(8);
     628                $this->skipBits(8);
     629                $profile = $this->getBits(8);   //      read profile
     630                $this->skipBits(16);
     631                $this->expGolombUe();   //      read sps id
     632                if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) {
     633                        if ($this->expGolombUe() == 3) {
     634                                $this->skipBits(1);
     635                        }
     636                        $this->expGolombUe();
     637                        $this->expGolombUe();
     638                        $this->skipBits(1);
     639                        if ($this->getBit()) {
     640                                for ($i = 0; $i < 8; $i++) {
     641                                        if ($this->getBit()) {
     642                                                $size = $i < 6 ? 16 : 64;
     643                                                $lastScale = 8;
     644                                                $nextScale = 8;
     645                                                for ($j = 0; $j < $size; $j++) {
     646                                                        if ($nextScale != 0) {
     647                                                                $deltaScale = $this->expGolombUe();
     648                                                                $nextScale = ($lastScale + $deltaScale + 256) % 256;
     649                                                        }
     650                                                        if ($nextScale != 0) {
     651                                                                $lastScale = $nextScale;
     652                                                        }
     653                                                }
     654                                        }
     655                                }
     656                        }
     657                }
     658                $this->expGolombUe();
     659                $pocType = $this->expGolombUe();
     660                if ($pocType == 0) {
     661                        $this->expGolombUe();
     662                } elseif ($pocType == 1) {
     663                        $this->skipBits(1);
     664                        $this->expGolombSe();
     665                        $this->expGolombSe();
     666                        $pocCycleLength = $this->expGolombUe();
     667                        for ($i = 0; $i < $pocCycleLength; $i++) {
     668                                $this->expGolombSe();
     669                        }
     670                }
     671                $this->expGolombUe();
     672                $this->skipBits(1);
     673                $this->width = ($this->expGolombUe() + 1) * 16;
     674                $heightMap = $this->expGolombUe() + 1;
     675                $this->height = (2 - $this->getBit()) * $heightMap * 16;
     676        }
     677
     678        public function skipBits($bits) {
     679                $newBits = $this->currentBits + $bits;
     680                $this->currentBytes += (int)floor($newBits / 8);
     681                $this->currentBits = $newBits % 8;
     682        }
     683
     684        public function getBit() {
     685                $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
     686                $this->skipBits(1);
     687                return $result;
     688        }
     689
     690        public function getBits($bits) {
     691                $result = 0;
     692                for ($i = 0; $i < $bits; $i++) {
     693                        $result = ($result << 1) + $this->getBit();
     694                }
     695                return $result;
     696        }
     697
     698        public function expGolombUe() {
     699                $significantBits = 0;
     700                $bit = $this->getBit();
     701                while ($bit == 0) {
     702                        $significantBits++;
     703                        $bit = $this->getBit();
     704
     705                        if ($significantBits > 31) {
     706                                // something is broken, this is an emergency escape to prevent infinite loops
     707                                return 0;
     708                        }
     709                }
     710                return (1 << $significantBits) + $this->getBits($significantBits) - 1;
     711        }
     712
     713        public function expGolombSe() {
     714                $result = $this->expGolombUe();
     715                if (($result & 0x01) == 0) {
     716                        return -($result >> 1);
     717                } else {
     718                        return ($result + 1) >> 1;
     719                }
     720        }
     721
     722        public function getWidth() {
     723                return $this->width;
     724        }
     725
     726        public function getHeight() {
     727                return $this->height;
     728        }
     729}
  • new file wp-includes/ID3/module.audio-video.matroska.php

    diff --git wp-includes/ID3/module.audio-video.matroska.php wp-includes/ID3/module.audio-video.matroska.php
    new file mode 100644
    index 0000000..3c1921f
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio-video.matriska.php                             //
     11// module for analyzing Matroska containers                    //
     12// dependencies: NONE                                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16
     17define('EBML_ID_CHAPTERS',                  0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation.
     18define('EBML_ID_SEEKHEAD',                  0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements.
     19define('EBML_ID_TAGS',                      0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>.
     20define('EBML_ID_INFO',                      0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file.
     21define('EBML_ID_TRACKS',                    0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described.
     22define('EBML_ID_SEGMENT',                   0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment.
     23define('EBML_ID_ATTACHMENTS',               0x0941A469); // [19][41][A4][69] -- Contain attached files.
     24define('EBML_ID_EBML',                      0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this.
     25define('EBML_ID_CUES',                      0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment.
     26define('EBML_ID_CLUSTER',                   0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure.
     27define('EBML_ID_LANGUAGE',                    0x02B59C); //     [22][B5][9C] -- Specifies the language of the track in the Matroska languages form.
     28define('EBML_ID_TRACKTIMECODESCALE',          0x03314F); //     [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs).
     29define('EBML_ID_DEFAULTDURATION',             0x03E383); //     [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame.
     30define('EBML_ID_CODECNAME',                   0x058688); //     [25][86][88] -- A human-readable string specifying the codec.
     31define('EBML_ID_CODECDOWNLOADURL',            0x06B240); //     [26][B2][40] -- A URL to download about the codec used.
     32define('EBML_ID_TIMECODESCALE',               0x0AD7B1); //     [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds).
     33define('EBML_ID_COLOURSPACE',                 0x0EB524); //     [2E][B5][24] -- Same value as in AVI (32 bits).
     34define('EBML_ID_GAMMAVALUE',                  0x0FB523); //     [2F][B5][23] -- Gamma Value.
     35define('EBML_ID_CODECSETTINGS',               0x1A9697); //     [3A][96][97] -- A string describing the encoding setting used.
     36define('EBML_ID_CODECINFOURL',                0x1B4040); //     [3B][40][40] -- A URL to find information about the codec used.
     37define('EBML_ID_PREVFILENAME',                0x1C83AB); //     [3C][83][AB] -- An escaped filename corresponding to the previous segment.
     38define('EBML_ID_PREVUID',                     0x1CB923); //     [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits).
     39define('EBML_ID_NEXTFILENAME',                0x1E83BB); //     [3E][83][BB] -- An escaped filename corresponding to the next segment.
     40define('EBML_ID_NEXTUID',                     0x1EB923); //     [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits).
     41define('EBML_ID_CONTENTCOMPALGO',               0x0254); //         [42][54] -- The compression algorithm used. Algorithms that have been specified so far are:
     42define('EBML_ID_CONTENTCOMPSETTINGS',           0x0255); //         [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track.
     43define('EBML_ID_DOCTYPE',                       0x0282); //         [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case).
     44define('EBML_ID_DOCTYPEREADVERSION',            0x0285); //         [42][85] -- The minimum DocType version an interpreter has to support to read this file.
     45define('EBML_ID_EBMLVERSION',                   0x0286); //         [42][86] -- The version of EBML parser used to create the file.
     46define('EBML_ID_DOCTYPEVERSION',                0x0287); //         [42][87] -- The version of DocType interpreter used to create the file.
     47define('EBML_ID_EBMLMAXIDLENGTH',               0x02F2); //         [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska).
     48define('EBML_ID_EBMLMAXSIZELENGTH',             0x02F3); //         [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid.
     49define('EBML_ID_EBMLREADVERSION',               0x02F7); //         [42][F7] -- The minimum EBML version a parser has to support to read this file.
     50define('EBML_ID_CHAPLANGUAGE',                  0x037C); //         [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form.
     51define('EBML_ID_CHAPCOUNTRY',                   0x037E); //         [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains.
     52define('EBML_ID_SEGMENTFAMILY',                 0x0444); //         [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits).
     53define('EBML_ID_DATEUTC',                       0x0461); //         [44][61] -- Date of the origin of timecode (value 0), i.e. production date.
     54define('EBML_ID_TAGLANGUAGE',                   0x047A); //         [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form.
     55define('EBML_ID_TAGDEFAULT',                    0x0484); //         [44][84] -- Indication to know if this is the default/original language to use for the given tag.
     56define('EBML_ID_TAGBINARY',                     0x0485); //         [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString.
     57define('EBML_ID_TAGSTRING',                     0x0487); //         [44][87] -- The value of the Tag.
     58define('EBML_ID_DURATION',                      0x0489); //         [44][89] -- Duration of the segment (based on TimecodeScale).
     59define('EBML_ID_CHAPPROCESSPRIVATE',            0x050D); //         [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent.
     60define('EBML_ID_CHAPTERFLAGENABLED',            0x0598); //         [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter.
     61define('EBML_ID_TAGNAME',                       0x05A3); //         [45][A3] -- The name of the Tag that is going to be stored.
     62define('EBML_ID_EDITIONENTRY',                  0x05B9); //         [45][B9] -- Contains all information about a segment edition.
     63define('EBML_ID_EDITIONUID',                    0x05BC); //         [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition.
     64define('EBML_ID_EDITIONFLAGHIDDEN',             0x05BD); //         [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks).
     65define('EBML_ID_EDITIONFLAGDEFAULT',            0x05DB); //         [45][DB] -- If a flag is set (1) the edition should be used as the default one.
     66define('EBML_ID_EDITIONFLAGORDERED',            0x05DD); //         [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced.
     67define('EBML_ID_FILEDATA',                      0x065C); //         [46][5C] -- The data of the file.
     68define('EBML_ID_FILEMIMETYPE',                  0x0660); //         [46][60] -- MIME type of the file.
     69define('EBML_ID_FILENAME',                      0x066E); //         [46][6E] -- Filename of the attached file.
     70define('EBML_ID_FILEREFERRAL',                  0x0675); //         [46][75] -- A binary value that a track/codec can refer to when the attachment is needed.
     71define('EBML_ID_FILEDESCRIPTION',               0x067E); //         [46][7E] -- A human-friendly name for the attached file.
     72define('EBML_ID_FILEUID',                       0x06AE); //         [46][AE] -- Unique ID representing the file, as random as possible.
     73define('EBML_ID_CONTENTENCALGO',                0x07E1); //         [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values:
     74define('EBML_ID_CONTENTENCKEYID',               0x07E2); //         [47][E2] -- For public key algorithms this is the ID of the public key the the data was encrypted with.
     75define('EBML_ID_CONTENTSIGNATURE',              0x07E3); //         [47][E3] -- A cryptographic signature of the contents.
     76define('EBML_ID_CONTENTSIGKEYID',               0x07E4); //         [47][E4] -- This is the ID of the private key the data was signed with.
     77define('EBML_ID_CONTENTSIGALGO',                0x07E5); //         [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
     78define('EBML_ID_CONTENTSIGHASHALGO',            0x07E6); //         [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
     79define('EBML_ID_MUXINGAPP',                     0x0D80); //         [4D][80] -- Muxing application or library ("libmatroska-0.4.3").
     80define('EBML_ID_SEEK',                          0x0DBB); //         [4D][BB] -- Contains a single seek entry to an EBML element.
     81define('EBML_ID_CONTENTENCODINGORDER',          0x1031); //         [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment.
     82define('EBML_ID_CONTENTENCODINGSCOPE',          0x1032); //         [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values:
     83define('EBML_ID_CONTENTENCODINGTYPE',           0x1033); //         [50][33] -- A value describing what kind of transformation has been done. Possible values:
     84define('EBML_ID_CONTENTCOMPRESSION',            0x1034); //         [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking.
     85define('EBML_ID_CONTENTENCRYPTION',             0x1035); //         [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise.
     86define('EBML_ID_CUEREFNUMBER',                  0x135F); //         [53][5F] -- Number of the referenced Block of Track X in the specified Cluster.
     87define('EBML_ID_NAME',                          0x136E); //         [53][6E] -- A human-readable track name.
     88define('EBML_ID_CUEBLOCKNUMBER',                0x1378); //         [53][78] -- Number of the Block in the specified Cluster.
     89define('EBML_ID_TRACKOFFSET',                   0x137F); //         [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track.
     90define('EBML_ID_SEEKID',                        0x13AB); //         [53][AB] -- The binary ID corresponding to the element name.
     91define('EBML_ID_SEEKPOSITION',                  0x13AC); //         [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element).
     92define('EBML_ID_STEREOMODE',                    0x13B8); //         [53][B8] -- Stereo-3D video mode.
     93define('EBML_ID_OLDSTEREOMODE',                 0x13B9); //         [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes).
     94define('EBML_ID_PIXELCROPBOTTOM',               0x14AA); //         [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content).
     95define('EBML_ID_DISPLAYWIDTH',                  0x14B0); //         [54][B0] -- Width of the video frames to display.
     96define('EBML_ID_DISPLAYUNIT',                   0x14B2); //         [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches).
     97define('EBML_ID_ASPECTRATIOTYPE',               0x14B3); //         [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed).
     98define('EBML_ID_DISPLAYHEIGHT',                 0x14BA); //         [54][BA] -- Height of the video frames to display.
     99define('EBML_ID_PIXELCROPTOP',                  0x14BB); //         [54][BB] -- The number of video pixels to remove at the top of the image.
     100define('EBML_ID_PIXELCROPLEFT',                 0x14CC); //         [54][CC] -- The number of video pixels to remove on the left of the image.
     101define('EBML_ID_PIXELCROPRIGHT',                0x14DD); //         [54][DD] -- The number of video pixels to remove on the right of the image.
     102define('EBML_ID_FLAGFORCED',                    0x15AA); //         [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind.
     103define('EBML_ID_MAXBLOCKADDITIONID',            0x15EE); //         [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track.
     104define('EBML_ID_WRITINGAPP',                    0x1741); //         [57][41] -- Writing application ("mkvmerge-0.3.3").
     105define('EBML_ID_CLUSTERSILENTTRACKS',           0x1854); //         [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use.
     106define('EBML_ID_CLUSTERSILENTTRACKNUMBER',      0x18D7); //         [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster.
     107define('EBML_ID_ATTACHEDFILE',                  0x21A7); //         [61][A7] -- An attached file.
     108define('EBML_ID_CONTENTENCODING',               0x2240); //         [62][40] -- Settings for one content encoding like compression or encryption.
     109define('EBML_ID_BITDEPTH',                      0x2264); //         [62][64] -- Bits per sample, mostly used for PCM.
     110define('EBML_ID_CODECPRIVATE',                  0x23A2); //         [63][A2] -- Private data only known to the codec.
     111define('EBML_ID_TARGETS',                       0x23C0); //         [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment.
     112define('EBML_ID_CHAPTERPHYSICALEQUIV',          0x23C3); //         [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values.
     113define('EBML_ID_TAGCHAPTERUID',                 0x23C4); //         [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment.
     114define('EBML_ID_TAGTRACKUID',                   0x23C5); //         [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment.
     115define('EBML_ID_TAGATTACHMENTUID',              0x23C6); //         [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment.
     116define('EBML_ID_TAGEDITIONUID',                 0x23C9); //         [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment.
     117define('EBML_ID_TARGETTYPE',                    0x23CA); //         [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType).
     118define('EBML_ID_TRACKTRANSLATE',                0x2624); //         [66][24] -- The track identification for the given Chapter Codec.
     119define('EBML_ID_TRACKTRANSLATETRACKID',         0x26A5); //         [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used.
     120define('EBML_ID_TRACKTRANSLATECODEC',           0x26BF); //         [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
     121define('EBML_ID_TRACKTRANSLATEEDITIONUID',      0x26FC); //         [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment.
     122define('EBML_ID_SIMPLETAG',                     0x27C8); //         [67][C8] -- Contains general information about the target.
     123define('EBML_ID_TARGETTYPEVALUE',               0x28CA); //         [68][CA] -- A number to indicate the logical level of the target (see TargetType).
     124define('EBML_ID_CHAPPROCESSCOMMAND',            0x2911); //         [69][11] -- Contains all the commands associated to the Atom.
     125define('EBML_ID_CHAPPROCESSTIME',               0x2922); //         [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter).
     126define('EBML_ID_CHAPTERTRANSLATE',              0x2924); //         [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment.
     127define('EBML_ID_CHAPPROCESSDATA',               0x2933); //         [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands.
     128define('EBML_ID_CHAPPROCESS',                   0x2944); //         [69][44] -- Contains all the commands associated to the Atom.
     129define('EBML_ID_CHAPPROCESSCODECID',            0x2955); //         [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later.
     130define('EBML_ID_CHAPTERTRANSLATEID',            0x29A5); //         [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used.
     131define('EBML_ID_CHAPTERTRANSLATECODEC',         0x29BF); //         [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
     132define('EBML_ID_CHAPTERTRANSLATEEDITIONUID',    0x29FC); //         [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment.
     133define('EBML_ID_CONTENTENCODINGS',              0x2D80); //         [6D][80] -- Settings for several content encoding mechanisms like compression or encryption.
     134define('EBML_ID_MINCACHE',                      0x2DE7); //         [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used.
     135define('EBML_ID_MAXCACHE',                      0x2DF8); //         [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed.
     136define('EBML_ID_CHAPTERSEGMENTUID',             0x2E67); //         [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used.
     137define('EBML_ID_CHAPTERSEGMENTEDITIONUID',      0x2EBC); //         [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID.
     138define('EBML_ID_TRACKOVERLAY',                  0x2FAB); //         [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc.
     139define('EBML_ID_TAG',                           0x3373); //         [73][73] -- Element containing elements specific to Tracks/Chapters.
     140define('EBML_ID_SEGMENTFILENAME',               0x3384); //         [73][84] -- A filename corresponding to this segment.
     141define('EBML_ID_SEGMENTUID',                    0x33A4); //         [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits).
     142define('EBML_ID_CHAPTERUID',                    0x33C4); //         [73][C4] -- A unique ID to identify the Chapter.
     143define('EBML_ID_TRACKUID',                      0x33C5); //         [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file.
     144define('EBML_ID_ATTACHMENTLINK',                0x3446); //         [74][46] -- The UID of an attachment that is used by this codec.
     145define('EBML_ID_CLUSTERBLOCKADDITIONS',         0x35A1); //         [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data.
     146define('EBML_ID_CHANNELPOSITIONS',              0x347B); //         [7D][7B] -- Table of horizontal angles for each successive channel, see appendix.
     147define('EBML_ID_OUTPUTSAMPLINGFREQUENCY',       0x38B5); //         [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques).
     148define('EBML_ID_TITLE',                         0x3BA9); //         [7B][A9] -- General name of the segment.
     149define('EBML_ID_CHAPTERDISPLAY',                  0x00); //             [80] -- Contains all possible strings to use for the chapter display.
     150define('EBML_ID_TRACKTYPE',                       0x03); //             [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control).
     151define('EBML_ID_CHAPSTRING',                      0x05); //             [85] -- Contains the string to use as the chapter atom.
     152define('EBML_ID_CODECID',                         0x06); //             [86] -- An ID corresponding to the codec, see the codec page for more info.
     153define('EBML_ID_FLAGDEFAULT',                     0x08); //             [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference.
     154define('EBML_ID_CHAPTERTRACKNUMBER',              0x09); //             [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks.
     155define('EBML_ID_CLUSTERSLICES',                   0x0E); //             [8E] -- Contains slices description.
     156define('EBML_ID_CHAPTERTRACK',                    0x0F); //             [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply
     157define('EBML_ID_CHAPTERTIMESTART',                0x11); //             [91] -- Timecode of the start of Chapter (not scaled).
     158define('EBML_ID_CHAPTERTIMEEND',                  0x12); //             [92] -- Timecode of the end of Chapter (timecode excluded, not scaled).
     159define('EBML_ID_CUEREFTIME',                      0x16); //             [96] -- Timecode of the referenced Block.
     160define('EBML_ID_CUEREFCLUSTER',                   0x17); //             [97] -- Position of the Cluster containing the referenced Block.
     161define('EBML_ID_CHAPTERFLAGHIDDEN',               0x18); //             [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks).
     162define('EBML_ID_FLAGINTERLACED',                  0x1A); //             [9A] -- Set if the video is interlaced.
     163define('EBML_ID_CLUSTERBLOCKDURATION',            0x1B); //             [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks.
     164define('EBML_ID_FLAGLACING',                      0x1C); //             [9C] -- Set if the track may contain blocks using lacing.
     165define('EBML_ID_CHANNELS',                        0x1F); //             [9F] -- Numbers of channels in the track.
     166define('EBML_ID_CLUSTERBLOCKGROUP',               0x20); //             [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock.
     167define('EBML_ID_CLUSTERBLOCK',                    0x21); //             [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode.
     168define('EBML_ID_CLUSTERBLOCKVIRTUAL',             0x22); //             [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order.
     169define('EBML_ID_CLUSTERSIMPLEBLOCK',              0x23); //             [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed.
     170define('EBML_ID_CLUSTERCODECSTATE',               0x24); //             [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry.
     171define('EBML_ID_CLUSTERBLOCKADDITIONAL',          0x25); //             [A5] -- Interpreted by the codec as it wishes (using the BlockAddID).
     172define('EBML_ID_CLUSTERBLOCKMORE',                0x26); //             [A6] -- Contain the BlockAdditional and some parameters.
     173define('EBML_ID_CLUSTERPOSITION',                 0x27); //             [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams.
     174define('EBML_ID_CODECDECODEALL',                  0x2A); //             [AA] -- The codec can decode potentially damaged data.
     175define('EBML_ID_CLUSTERPREVSIZE',                 0x2B); //             [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing.
     176define('EBML_ID_TRACKENTRY',                      0x2E); //             [AE] -- Describes a track with all elements.
     177define('EBML_ID_CLUSTERENCRYPTEDBLOCK',           0x2F); //             [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed).
     178define('EBML_ID_PIXELWIDTH',                      0x30); //             [B0] -- Width of the encoded video frames in pixels.
     179define('EBML_ID_CUETIME',                         0x33); //             [B3] -- Absolute timecode according to the segment time base.
     180define('EBML_ID_SAMPLINGFREQUENCY',               0x35); //             [B5] -- Sampling frequency in Hz.
     181define('EBML_ID_CHAPTERATOM',                     0x36); //             [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks).
     182define('EBML_ID_CUETRACKPOSITIONS',               0x37); //             [B7] -- Contain positions for different tracks corresponding to the timecode.
     183define('EBML_ID_FLAGENABLED',                     0x39); //             [B9] -- Set if the track is used.
     184define('EBML_ID_PIXELHEIGHT',                     0x3A); //             [BA] -- Height of the encoded video frames in pixels.
     185define('EBML_ID_CUEPOINT',                        0x3B); //             [BB] -- Contains all information relative to a seek point in the segment.
     186define('EBML_ID_CRC32',                           0x3F); //             [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32.
     187define('EBML_ID_CLUSTERBLOCKADDITIONID',          0x4B); //             [CB] -- The ID of the BlockAdditional element (0 is the main Block).
     188define('EBML_ID_CLUSTERLACENUMBER',               0x4C); //             [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
     189define('EBML_ID_CLUSTERFRAMENUMBER',              0x4D); //             [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame).
     190define('EBML_ID_CLUSTERDELAY',                    0x4E); //             [CE] -- The (scaled) delay to apply to the element.
     191define('EBML_ID_CLUSTERDURATION',                 0x4F); //             [CF] -- The (scaled) duration to apply to the element.
     192define('EBML_ID_TRACKNUMBER',                     0x57); //             [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number).
     193define('EBML_ID_CUEREFERENCE',                    0x5B); //             [DB] -- The Clusters containing the required referenced Blocks.
     194define('EBML_ID_VIDEO',                           0x60); //             [E0] -- Video settings.
     195define('EBML_ID_AUDIO',                           0x61); //             [E1] -- Audio settings.
     196define('EBML_ID_CLUSTERTIMESLICE',                0x68); //             [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
     197define('EBML_ID_CUECODECSTATE',                   0x6A); //             [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry.
     198define('EBML_ID_CUEREFCODECSTATE',                0x6B); //             [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry.
     199define('EBML_ID_VOID',                            0x6C); //             [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use.
     200define('EBML_ID_CLUSTERTIMECODE',                 0x67); //             [E7] -- Absolute timecode of the cluster (based on TimecodeScale).
     201define('EBML_ID_CLUSTERBLOCKADDID',               0x6E); //             [EE] -- An ID to identify the BlockAdditional level.
     202define('EBML_ID_CUECLUSTERPOSITION',              0x71); //             [F1] -- The position of the Cluster containing the required Block.
     203define('EBML_ID_CUETRACK',                        0x77); //             [F7] -- The track for which a position is given.
     204define('EBML_ID_CLUSTERREFERENCEPRIORITY',        0x7A); //             [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced.
     205define('EBML_ID_CLUSTERREFERENCEBLOCK',           0x7B); //             [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to.
     206define('EBML_ID_CLUSTERREFERENCEVIRTUAL',         0x7D); //             [FD] -- Relative position of the data that should be in position of the virtual block.
     207
     208
     209/**
     210* @tutorial http://www.matroska.org/technical/specs/index.html
     211*
     212* @todo Rewrite EBML parser to reduce it's size and honor default element values
     213* @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection
     214*/
     215class getid3_matroska extends getid3_handler
     216{
     217        // public options
     218        public static $hide_clusters    = true;  // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE]
     219    public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE]
     220
     221    // private parser settings/placeholders
     222    private $EBMLbuffer        = '';
     223    private $EBMLbuffer_offset = 0;
     224    private $EBMLbuffer_length = 0;
     225    private $current_offset    = 0;
     226    private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID);
     227
     228        public function Analyze()
     229        {
     230                $info = &$this->getid3->info;
     231
     232                // parse container
     233                try {
     234                        $this->parseEBML($info);
     235                } catch (Exception $e) {
     236                        $info['error'][] = 'EBML parser: '.$e->getMessage();
     237                }
     238
     239                // calculate playtime
     240                if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
     241                        foreach ($info['matroska']['info'] as $key => $infoarray) {
     242                                if (isset($infoarray['Duration'])) {
     243                                        // TimecodeScale is how many nanoseconds each Duration unit is
     244                                        $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000);
     245                                        break;
     246                                }
     247                        }
     248                }
     249
     250                // extract tags
     251                if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) {
     252                        foreach ($info['matroska']['tags'] as $key => $infoarray) {
     253                                $this->ExtractCommentsSimpleTag($infoarray);
     254                        }
     255                }
     256
     257                // process tracks
     258                if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
     259                        foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) {
     260
     261                                $track_info = array();
     262                                $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']);
     263                                $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true);
     264                                if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; }
     265
     266                                switch ($trackarray['TrackType']) {
     267
     268                                        case 1: // Video
     269                                                $track_info['resolution_x'] = $trackarray['PixelWidth'];
     270                                                $track_info['resolution_y'] = $trackarray['PixelHeight'];
     271                                                $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0);
     272                                                $track_info['display_x']    = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']);
     273                                                $track_info['display_y']    = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']);
     274
     275                                                if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; }
     276                                                if (isset($trackarray['PixelCropTop']))    { $track_info['crop_top']    = $trackarray['PixelCropTop']; }
     277                                                if (isset($trackarray['PixelCropLeft']))   { $track_info['crop_left']   = $trackarray['PixelCropLeft']; }
     278                                                if (isset($trackarray['PixelCropRight']))  { $track_info['crop_right']  = $trackarray['PixelCropRight']; }
     279                                                if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate']  = round(1000000000 / $trackarray['DefaultDuration'], 3); }
     280                                                if (isset($trackarray['CodecName']))       { $track_info['codec']       = $trackarray['CodecName']; }
     281
     282                                                switch ($trackarray['CodecID']) {
     283                                                        case 'V_MS/VFW/FOURCC':
     284                                                                if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) {
     285                                                                        $this->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"');
     286                                                                        break;
     287                                                                }
     288                                                                $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']);
     289                                                                $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']);
     290                                                                $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed;
     291                                                                break;
     292
     293                                                        /*case 'V_MPEG4/ISO/AVC':
     294                                                                $h264['profile']    = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1));
     295                                                                $h264['level']      = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1));
     296                                                                $rn                 = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1));
     297                                                                $h264['NALUlength'] = ($rn & 3) + 1;
     298                                                                $rn                 = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1));
     299                                                                $nsps               = ($rn & 31);
     300                                                                $offset             = 6;
     301                                                                for ($i = 0; $i < $nsps; $i ++) {
     302                                                                        $length        = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2));
     303                                                                        $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length);
     304                                                                        $offset       += 2 + $length;
     305                                                                }
     306                                                                $npps               = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1));
     307                                                                $offset            += 1;
     308                                                                for ($i = 0; $i < $npps; $i ++) {
     309                                                                        $length        = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2));
     310                                                                        $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length);
     311                                                                        $offset       += 2 + $length;
     312                                                                }
     313                                                                $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264;
     314                                                                break;*/
     315                                                }
     316
     317                                                $info['video']['streams'][] = $track_info;
     318                                                break;
     319
     320                                        case 2: // Audio
     321                                                $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0);
     322                                                $track_info['channels']    = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1);
     323                                                $track_info['language']    = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng');
     324                                                if (isset($trackarray['BitDepth']))  { $track_info['bits_per_sample'] = $trackarray['BitDepth']; }
     325                                                if (isset($trackarray['CodecName'])) { $track_info['codec']           = $trackarray['CodecName']; }
     326
     327                                                switch ($trackarray['CodecID']) {
     328                                                        case 'A_PCM/INT/LIT':
     329                                                        case 'A_PCM/INT/BIG':
     330                                                                $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth'];
     331                                                                break;
     332
     333                                                        case 'A_AC3':
     334                                                        case 'A_DTS':
     335                                                        case 'A_MPEG/L3':
     336                                                        case 'A_MPEG/L2':
     337                                                        case 'A_FLAC':
     338                                                                if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']).'.php', __FILE__, false)) {
     339                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"');
     340                                                                        break;
     341                                                                }
     342
     343                                                                if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) {
     344                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set');
     345                                                                        break;
     346                                                                }
     347
     348                                                                // create temp instance
     349                                                                $getid3_temp = new getID3();
     350                                                                if ($track_info['dataformat'] != 'flac') {
     351                                                                        $getid3_temp->openfile($this->getid3->filename);
     352                                                                }
     353                                                                $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'];
     354                                                                if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') {
     355                                                                        $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length'];
     356                                                                }
     357
     358                                                                // analyze
     359                                                                $class = 'getid3_'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']);
     360                                                                $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat'];
     361                                                                $getid3_audio = new $class($getid3_temp, __CLASS__);
     362                                                                if ($track_info['dataformat'] == 'flac') {
     363                                                                        $getid3_audio->AnalyzeString($trackarray['CodecPrivate']);
     364                                                                }
     365                                                                else {
     366                                                                        $getid3_audio->Analyze();
     367                                                                }
     368                                                                if (!empty($getid3_temp->info[$header_data_key])) {
     369                                                                        $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key];
     370                                                                        if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) {
     371                                                                                foreach ($getid3_temp->info['audio'] as $key => $value) {
     372                                                                                        $track_info[$key] = $value;
     373                                                                                }
     374                                                                        }
     375                                                                }
     376                                                                else {
     377                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']);
     378                                                                }
     379
     380                                                                // copy errors and warnings
     381                                                                if (!empty($getid3_temp->info['error'])) {
     382                                                                        foreach ($getid3_temp->info['error'] as $newerror) {
     383                                                                                $this->warning($class.'() says: ['.$newerror.']');
     384                                                                        }
     385                                                                }
     386                                                                if (!empty($getid3_temp->info['warning'])) {
     387                                                                        foreach ($getid3_temp->info['warning'] as $newerror) {
     388                                                                                if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) {
     389                                                                                        // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning
     390                                                                                        continue;
     391                                                                                }
     392                                                                                $this->warning($class.'() says: ['.$newerror.']');
     393                                                                        }
     394                                                                }
     395                                                                unset($getid3_temp, $getid3_audio);
     396                                                                break;
     397
     398                                                        case 'A_AAC':
     399                                                        case 'A_AAC/MPEG2/LC':
     400                                                        case 'A_AAC/MPEG2/LC/SBR':
     401                                                        case 'A_AAC/MPEG4/LC':
     402                                                        case 'A_AAC/MPEG4/LC/SBR':
     403                                                            $this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated');
     404                                                                break;
     405
     406                                                        case 'A_VORBIS':
     407                                                                if (!isset($trackarray['CodecPrivate'])) {
     408                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set');
     409                                                                        break;
     410                                                                }
     411                                                                $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1);
     412                                                                if ($vorbis_offset === false) {
     413                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword');
     414                                                                        break;
     415                                                                }
     416                                                                $vorbis_offset -= 1;
     417
     418                                                                if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) {
     419                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"');
     420                                                                        break;
     421                                                                }
     422
     423                                                                // create temp instance
     424                                                                $getid3_temp = new getID3();
     425
     426                                                                // analyze
     427                                                                $getid3_ogg = new getid3_ogg($getid3_temp);
     428                                                                $oggpageinfo['page_seqno'] = 0;
     429                                                                $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo);
     430                                                                if (!empty($getid3_temp->info['ogg'])) {
     431                                                                        $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg'];
     432                                                                        if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) {
     433                                                                                foreach ($getid3_temp->info['audio'] as $key => $value) {
     434                                                                                        $track_info[$key] = $value;
     435                                                                                }
     436                                                                        }
     437                                                                }
     438
     439                                                                // copy errors and warnings
     440                                                                if (!empty($getid3_temp->info['error'])) {
     441                                                                        foreach ($getid3_temp->info['error'] as $newerror) {
     442                                                                                $this->warning('getid3_ogg() says: ['.$newerror.']');
     443                                                                        }
     444                                                                }
     445                                                                if (!empty($getid3_temp->info['warning'])) {
     446                                                                        foreach ($getid3_temp->info['warning'] as $newerror) {
     447                                                                                $this->warning('getid3_ogg() says: ['.$newerror.']');
     448                                                                        }
     449                                                                }
     450
     451                                                                if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) {
     452                                                                        $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal'];
     453                                                                }
     454                                                                unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset);
     455                                                                break;
     456
     457                                                        case 'A_MS/ACM':
     458                                                                if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) {
     459                                                                        $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"');
     460                                                                        break;
     461                                                                }
     462
     463                                                                $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']);
     464                                                                foreach ($parsed as $key => $value) {
     465                                                                        if ($key != 'raw') {
     466                                                                                $track_info[$key] = $value;
     467                                                                        }
     468                                                                }
     469                                                                $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed;
     470                                                                break;
     471
     472                                                        default:
     473                                                                $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"');
     474                                                }
     475
     476                                                $info['audio']['streams'][] = $track_info;
     477                                                break;
     478                                }
     479                        }
     480
     481                        if (!empty($info['video']['streams'])) {
     482                                $info['video'] = self::getDefaultStreamInfo($info['video']['streams']);
     483                        }
     484                        if (!empty($info['audio']['streams'])) {
     485                                $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']);
     486                        }
     487                }
     488
     489                // process attachments
     490                if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) {
     491                        foreach ($info['matroska']['attachments'] as $i => $entry) {
     492                                if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) {
     493                                        $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']);
     494                                }
     495                        }
     496                }
     497
     498        // determine mime type
     499                if (!empty($info['video']['streams'])) {
     500                        $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska');
     501                } elseif (!empty($info['audio']['streams'])) {
     502                        $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska');
     503                } elseif (isset($info['mime_type'])) {
     504                        unset($info['mime_type']);
     505                }
     506
     507                return true;
     508        }
     509
     510    private function parseEBML(&$info) {
     511                // http://www.matroska.org/technical/specs/index.html#EBMLBasics
     512                $this->current_offset = $info['avdataoffset'];
     513
     514                while ($this->getEBMLelement($top_element, $info['avdataend'])) {
     515                        switch ($top_element['id']) {
     516
     517                                case EBML_ID_EBML:
     518                                        $info['fileformat'] = 'matroska';
     519                                        $info['matroska']['header']['offset'] = $top_element['offset'];
     520                                        $info['matroska']['header']['length'] = $top_element['length'];
     521
     522                                        while ($this->getEBMLelement($element_data, $top_element['end'], true)) {
     523                                                switch ($element_data['id']) {
     524
     525                                                        case EBML_ID_EBMLVERSION:
     526                                                        case EBML_ID_EBMLREADVERSION:
     527                                                        case EBML_ID_EBMLMAXIDLENGTH:
     528                                                        case EBML_ID_EBMLMAXSIZELENGTH:
     529                                                        case EBML_ID_DOCTYPEVERSION:
     530                                                        case EBML_ID_DOCTYPEREADVERSION:
     531                                                                $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']);
     532                                                                break;
     533
     534                                                        case EBML_ID_DOCTYPE:
     535                                                                $element_data['data'] = getid3_lib::trimNullByte($element_data['data']);
     536                                                                $info['matroska']['doctype'] = $element_data['data'];
     537                                                                break;
     538
     539                                                        case EBML_ID_CRC32: // not useful, ignore
     540                                                                $this->current_offset = $element_data['end'];
     541                                                                unset($element_data);
     542                                                                break;
     543
     544                                                        default:
     545                                                                $this->unhandledElement('header', __LINE__, $element_data);
     546                                                }
     547                                                if (!empty($element_data)) {
     548                                                        unset($element_data['offset'], $element_data['end']);
     549                                                        $info['matroska']['header']['elements'][] = $element_data;
     550                                                }
     551                                        }
     552                                        break;
     553
     554                                case EBML_ID_SEGMENT:
     555                                        $info['matroska']['segment'][0]['offset'] = $top_element['offset'];
     556                                        $info['matroska']['segment'][0]['length'] = $top_element['length'];
     557
     558                                        while ($this->getEBMLelement($element_data, $top_element['end'])) {
     559                                                if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required
     560                                                        $info['matroska']['segments'][] = $element_data;
     561                                                }
     562                                                switch ($element_data['id']) {
     563
     564                                                        case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements.
     565
     566                                                                while ($this->getEBMLelement($seek_entry, $element_data['end'])) {
     567                                                                        switch ($seek_entry['id']) {
     568
     569                                                                                case EBML_ID_SEEK: // Contains a single seek entry to an EBML element
     570                                                                                        while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) {
     571
     572                                                                                                switch ($sub_seek_entry['id']) {
     573
     574                                                                                                        case EBML_ID_SEEKID:
     575                                                                                                                $seek_entry['target_id']   = self::EBML2Int($sub_seek_entry['data']);
     576                                                                                                                $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']);
     577                                                                                                                break;
     578
     579                                                                                                        case EBML_ID_SEEKPOSITION:
     580                                                                                                                $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']);
     581                                                                                                                break;
     582
     583                                                                                                        default:
     584                                                                                                                $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry);                                                                                            }
     585                                                                                        }
     586
     587                                                                                        if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required
     588                                                                                                $info['matroska']['seek'][] = $seek_entry;
     589                                                                                        }
     590                                                                                        break;
     591
     592                                                                                default:
     593                                                                                        $this->unhandledElement('seekhead', __LINE__, $seek_entry);
     594                                                                        }
     595                                                                }
     596                                                                break;
     597
     598                                                        case EBML_ID_TRACKS: // A top-level block of information with many tracks described.
     599                                                                $info['matroska']['tracks'] = $element_data;
     600
     601                                                                while ($this->getEBMLelement($track_entry, $element_data['end'])) {
     602                                                                        switch ($track_entry['id']) {
     603
     604                                                                                case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements.
     605
     606                                                                                        while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) {
     607                                                                                                switch ($subelement['id']) {
     608
     609                                                                                                        case EBML_ID_TRACKNUMBER:
     610                                                                                                        case EBML_ID_TRACKUID:
     611                                                                                                        case EBML_ID_TRACKTYPE:
     612                                                                                                        case EBML_ID_MINCACHE:
     613                                                                                                        case EBML_ID_MAXCACHE:
     614                                                                                                        case EBML_ID_MAXBLOCKADDITIONID:
     615                                                                                                        case EBML_ID_DEFAULTDURATION: // nanoseconds per frame
     616                                                                                                                $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
     617                                                                                                                break;
     618
     619                                                                                                        case EBML_ID_TRACKTIMECODESCALE:
     620                                                                                                                $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']);
     621                                                                                                                break;
     622
     623                                                                                                        case EBML_ID_CODECID:
     624                                                                                                        case EBML_ID_LANGUAGE:
     625                                                                                                        case EBML_ID_NAME:
     626                                                                                                        case EBML_ID_CODECNAME:
     627                                                                                                                $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
     628                                                                                                                break;
     629
     630                                                                                                        case EBML_ID_CODECPRIVATE:
     631                                                                                                                $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true);
     632                                                                                                                break;
     633
     634                                                                                                        case EBML_ID_FLAGENABLED:
     635                                                                                                        case EBML_ID_FLAGDEFAULT:
     636                                                                                                        case EBML_ID_FLAGFORCED:
     637                                                                                                        case EBML_ID_FLAGLACING:
     638                                                                                                        case EBML_ID_CODECDECODEALL:
     639                                                                                                                $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']);
     640                                                                                                                break;
     641
     642                                                                                                        case EBML_ID_VIDEO:
     643
     644                                                                                                                while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
     645                                                                                                                        switch ($sub_subelement['id']) {
     646
     647                                                                                                                                case EBML_ID_PIXELWIDTH:
     648                                                                                                                                case EBML_ID_PIXELHEIGHT:
     649                                                                                                                                case EBML_ID_PIXELCROPBOTTOM:
     650                                                                                                                                case EBML_ID_PIXELCROPTOP:
     651                                                                                                                                case EBML_ID_PIXELCROPLEFT:
     652                                                                                                                                case EBML_ID_PIXELCROPRIGHT:
     653                                                                                                                                case EBML_ID_DISPLAYWIDTH:
     654                                                                                                                                case EBML_ID_DISPLAYHEIGHT:
     655                                                                                                                                case EBML_ID_DISPLAYUNIT:
     656                                                                                                                                case EBML_ID_ASPECTRATIOTYPE:
     657                                                                                                                                case EBML_ID_STEREOMODE:
     658                                                                                                                                case EBML_ID_OLDSTEREOMODE:
     659                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     660                                                                                                                                        break;
     661
     662                                                                                                                                case EBML_ID_FLAGINTERLACED:
     663                                                                                                                                        $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']);
     664                                                                                                                                        break;
     665
     666                                                                                                                                case EBML_ID_GAMMAVALUE:
     667                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']);
     668                                                                                                                                        break;
     669
     670                                                                                                                                case EBML_ID_COLOURSPACE:
     671                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
     672                                                                                                                                        break;
     673
     674                                                                                                                                default:
     675                                                                                                                                        $this->unhandledElement('track.video', __LINE__, $sub_subelement);
     676                                                                                                                        }
     677                                                                                                                }
     678                                                                                                                break;
     679
     680                                                                                                        case EBML_ID_AUDIO:
     681
     682                                                                                                                while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
     683                                                                                                                        switch ($sub_subelement['id']) {
     684
     685                                                                                                                                case EBML_ID_CHANNELS:
     686                                                                                                                                case EBML_ID_BITDEPTH:
     687                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     688                                                                                                                                        break;
     689
     690                                                                                                                                case EBML_ID_SAMPLINGFREQUENCY:
     691                                                                                                                                case EBML_ID_OUTPUTSAMPLINGFREQUENCY:
     692                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']);
     693                                                                                                                                        break;
     694
     695                                                                                                                                case EBML_ID_CHANNELPOSITIONS:
     696                                                                                                                                        $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
     697                                                                                                                                        break;
     698
     699                                                                                                                                default:
     700                                                                                                                                        $this->unhandledElement('track.audio', __LINE__, $sub_subelement);
     701                                                                                                                        }
     702                                                                                                                }
     703                                                                                                                break;
     704
     705                                                                                                        case EBML_ID_CONTENTENCODINGS:
     706
     707                                                                                                                while ($this->getEBMLelement($sub_subelement, $subelement['end'])) {
     708                                                                                                                        switch ($sub_subelement['id']) {
     709
     710                                                                                                                                case EBML_ID_CONTENTENCODING:
     711
     712                                                                                                                                        while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) {
     713                                                                                                                                                switch ($sub_sub_subelement['id']) {
     714
     715                                                                                                                                                        case EBML_ID_CONTENTENCODINGORDER:
     716                                                                                                                                                        case EBML_ID_CONTENTENCODINGSCOPE:
     717                                                                                                                                                        case EBML_ID_CONTENTENCODINGTYPE:
     718                                                                                                                                                                $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     719                                                                                                                                                                break;
     720
     721                                                                                                                                                        case EBML_ID_CONTENTCOMPRESSION:
     722
     723                                                                                                                                                                while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
     724                                                                                                                                                                        switch ($sub_sub_sub_subelement['id']) {
     725
     726                                                                                                                                                                                case EBML_ID_CONTENTCOMPALGO:
     727                                                                                                                                                                                        $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
     728                                                                                                                                                                                        break;
     729
     730                                                                                                                                                                                case EBML_ID_CONTENTCOMPSETTINGS:
     731                                                                                                                                                                                        $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
     732                                                                                                                                                                                        break;
     733
     734                                                                                                                                                                                default:
     735                                                                                                                                                                                        $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
     736                                                                                                                                                                        }
     737                                                                                                                                                                }
     738                                                                                                                                                                break;
     739
     740                                                                                                                                                        case EBML_ID_CONTENTENCRYPTION:
     741
     742                                                                                                                                                                while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
     743                                                                                                                                                                        switch ($sub_sub_sub_subelement['id']) {
     744
     745                                                                                                                                                                                case EBML_ID_CONTENTENCALGO:
     746                                                                                                                                                                                case EBML_ID_CONTENTSIGALGO:
     747                                                                                                                                                                                case EBML_ID_CONTENTSIGHASHALGO:
     748                                                                                                                                                                                        $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
     749                                                                                                                                                                                        break;
     750
     751                                                                                                                                                                                case EBML_ID_CONTENTENCKEYID:
     752                                                                                                                                                                                case EBML_ID_CONTENTSIGNATURE:
     753                                                                                                                                                                                case EBML_ID_CONTENTSIGKEYID:
     754                                                                                                                                                                                        $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
     755                                                                                                                                                                                        break;
     756
     757                                                                                                                                                                                default:
     758                                                                                                                                                                                        $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
     759                                                                                                                                                                        }
     760                                                                                                                                                                }
     761                                                                                                                                                                break;
     762
     763                                                                                                                                                        default:
     764                                                                                                                                                                $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement);
     765                                                                                                                                                }
     766                                                                                                                                        }
     767                                                                                                                                        break;
     768
     769                                                                                                                                default:
     770                                                                                                                                        $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement);
     771                                                                                                                        }
     772                                                                                                                }
     773                                                                                                                break;
     774
     775                                                                                                        default:
     776                                                                                                                $this->unhandledElement('track', __LINE__, $subelement);
     777                                                                                                }
     778                                                                                        }
     779
     780                                                                                        $info['matroska']['tracks']['tracks'][] = $track_entry;
     781                                                                                        break;
     782
     783                                                                                default:
     784                                                                                        $this->unhandledElement('tracks', __LINE__, $track_entry);
     785                                                                        }
     786                                                                }
     787                                                                break;
     788
     789                                                        case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file.
     790                                                                $info_entry = array();
     791
     792                                                                while ($this->getEBMLelement($subelement, $element_data['end'], true)) {
     793                                                                        switch ($subelement['id']) {
     794
     795                                                                                case EBML_ID_TIMECODESCALE:
     796                                                                                        $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
     797                                                                                        break;
     798
     799                                                                                case EBML_ID_DURATION:
     800                                                                                        $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']);
     801                                                                                        break;
     802
     803                                                                                case EBML_ID_DATEUTC:
     804                                                                                        $info_entry[$subelement['id_name']]         = getid3_lib::BigEndian2Int($subelement['data']);
     805                                                                                        $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]);
     806                                                                                        break;
     807
     808                                                                                case EBML_ID_SEGMENTUID:
     809                                                                                case EBML_ID_PREVUID:
     810                                                                                case EBML_ID_NEXTUID:
     811                                                                                        $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
     812                                                                                        break;
     813
     814                                                                                case EBML_ID_SEGMENTFAMILY:
     815                                                                                        $info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']);
     816                                                                                        break;
     817
     818                                                                                case EBML_ID_SEGMENTFILENAME:
     819                                                                                case EBML_ID_PREVFILENAME:
     820                                                                                case EBML_ID_NEXTFILENAME:
     821                                                                                case EBML_ID_TITLE:
     822                                                                                case EBML_ID_MUXINGAPP:
     823                                                                                case EBML_ID_WRITINGAPP:
     824                                                                                        $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
     825                                                                                        $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']];
     826                                                                                        break;
     827
     828                                                                                case EBML_ID_CHAPTERTRANSLATE:
     829                                                                                        $chaptertranslate_entry = array();
     830
     831                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
     832                                                                                                switch ($sub_subelement['id']) {
     833
     834                                                                                                        case EBML_ID_CHAPTERTRANSLATEEDITIONUID:
     835                                                                                                                $chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     836                                                                                                                break;
     837
     838                                                                                                        case EBML_ID_CHAPTERTRANSLATECODEC:
     839                                                                                                                $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     840                                                                                                                break;
     841
     842                                                                                                        case EBML_ID_CHAPTERTRANSLATEID:
     843                                                                                                                $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
     844                                                                                                                break;
     845
     846                                                                                                        default:
     847                                                                                                                $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement);
     848                                                                                                }
     849                                                                                        }
     850                                                                                        $info_entry[$subelement['id_name']] = $chaptertranslate_entry;
     851                                                                                        break;
     852
     853                                                                                default:
     854                                                                                        $this->unhandledElement('info', __LINE__, $subelement);
     855                                                                        }
     856                                                                }
     857                                                                $info['matroska']['info'][] = $info_entry;
     858                                                                break;
     859
     860                                                        case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams.
     861                                                                if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway
     862                                                                        $this->current_offset = $element_data['end'];
     863                                                                        break;
     864                                                                }
     865                                                                $cues_entry = array();
     866
     867                                                                while ($this->getEBMLelement($subelement, $element_data['end'])) {
     868                                                                        switch ($subelement['id']) {
     869
     870                                                                                case EBML_ID_CUEPOINT:
     871                                                                                        $cuepoint_entry = array();
     872
     873                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) {
     874                                                                                                switch ($sub_subelement['id']) {
     875
     876                                                                                                        case EBML_ID_CUETRACKPOSITIONS:
     877                                                        $cuetrackpositions_entry = array();
     878
     879                                                                                                                while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) {
     880                                                                                                                        switch ($sub_sub_subelement['id']) {
     881
     882                                                                                                                                case EBML_ID_CUETRACK:
     883                                                                                                                                case EBML_ID_CUECLUSTERPOSITION:
     884                                                                                                                                case EBML_ID_CUEBLOCKNUMBER:
     885                                                                                                                                case EBML_ID_CUECODECSTATE:
     886                                                                                                                                        $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     887                                                                                                                                        break;
     888
     889                                                                                                                                default:
     890                                                                                                                                        $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement);
     891                                                                                                                        }
     892                                                                                                                }
     893                                                                                                                $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry;
     894                                                                                                                break;
     895
     896                                                                                                        case EBML_ID_CUETIME:
     897                                                                                                                $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     898                                                                                                                break;
     899
     900                                                                                                        default:
     901                                                                                                                $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement);
     902                                                                                                }
     903                                                                                        }
     904                                                                                        $cues_entry[] = $cuepoint_entry;
     905                                                                                        break;
     906
     907                                                                                default:
     908                                                                                        $this->unhandledElement('cues', __LINE__, $subelement);
     909                                                                        }
     910                                                                }
     911                                                                $info['matroska']['cues'] = $cues_entry;
     912                                                                break;
     913
     914                                                        case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters.
     915                                $tags_entry = array();
     916
     917                                                                while ($this->getEBMLelement($subelement, $element_data['end'], false)) {
     918                                                                        switch ($subelement['id']) {
     919
     920                                                                                case EBML_ID_TAG:
     921                                                                                        $tag_entry = array();
     922
     923                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) {
     924                                                                                                switch ($sub_subelement['id']) {
     925
     926                                                                                                        case EBML_ID_TARGETS:
     927                                                                                                                $targets_entry = array();
     928
     929                                                                                                                while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) {
     930                                                                                                                        switch ($sub_sub_subelement['id']) {
     931
     932                                                                                                                                case EBML_ID_TARGETTYPEVALUE:
     933                                                                                                                                        $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     934                                                                                                                                        $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]);
     935                                                                                                                                        break;
     936
     937                                                                                                                                case EBML_ID_TARGETTYPE:
     938                                                                                                                                        $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data'];
     939                                                                                                                                        break;
     940
     941                                                                                                                                case EBML_ID_TAGTRACKUID:
     942                                                                                                                                case EBML_ID_TAGEDITIONUID:
     943                                                                                                                                case EBML_ID_TAGCHAPTERUID:
     944                                                                                                                                case EBML_ID_TAGATTACHMENTUID:
     945                                                                                                                                        $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     946                                                                                                                                        break;
     947
     948                                                                                                                                default:
     949                                                                                                                                        $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement);
     950                                                                                                                        }
     951                                                                                                                }
     952                                                                                                                $tag_entry[$sub_subelement['id_name']] = $targets_entry;
     953                                                                                                                break;
     954
     955                                                                                                        case EBML_ID_SIMPLETAG:
     956                                                                                                                $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']);
     957                                                                                                                break;
     958
     959                                                                                                        default:
     960                                                                                                                $this->unhandledElement('tags.tag', __LINE__, $sub_subelement);
     961                                                                                                }
     962                                                                                        }
     963                                                                                        $tags_entry[] = $tag_entry;
     964                                                                                        break;
     965
     966                                                                                default:
     967                                                                                        $this->unhandledElement('tags', __LINE__, $subelement);
     968                                                                        }
     969                                                                }
     970                                                                $info['matroska']['tags'] = $tags_entry;
     971                                                                break;
     972
     973                                                        case EBML_ID_ATTACHMENTS: // Contain attached files.
     974
     975                                                                while ($this->getEBMLelement($subelement, $element_data['end'])) {
     976                                                                        switch ($subelement['id']) {
     977
     978                                                                                case EBML_ID_ATTACHEDFILE:
     979                                                                                        $attachedfile_entry = array();
     980
     981                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) {
     982                                                                                                switch ($sub_subelement['id']) {
     983
     984                                                                                                        case EBML_ID_FILEDESCRIPTION:
     985                                                                                                        case EBML_ID_FILENAME:
     986                                                                                                        case EBML_ID_FILEMIMETYPE:
     987                                                                                                                $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data'];
     988                                                                                                                break;
     989
     990                                                                                                        case EBML_ID_FILEDATA:
     991                                                                                                                $attachedfile_entry['data_offset'] = $this->current_offset;
     992                                                                                                                $attachedfile_entry['data_length'] = $sub_subelement['length'];
     993
     994                                                                                                                $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment(
     995                                                                                                                        $attachedfile_entry['FileName'],
     996                                                                                                                        $attachedfile_entry['data_offset'],
     997                                                                                                                        $attachedfile_entry['data_length']);
     998
     999                                                                                                                $this->current_offset = $sub_subelement['end'];
     1000                                                                                                                break;
     1001
     1002                                                                                                        case EBML_ID_FILEUID:
     1003                                                                                                                $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     1004                                                                                                                break;
     1005
     1006                                                                                                        default:
     1007                                                                                                                $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement);
     1008                                                                                                }
     1009                                                                                        }
     1010                                                                                        $info['matroska']['attachments'][] = $attachedfile_entry;
     1011                                                                                        break;
     1012
     1013                                                                                default:
     1014                                                                                        $this->unhandledElement('attachments', __LINE__, $subelement);
     1015                                                                        }
     1016                                                                }
     1017                                                                break;
     1018
     1019                                                        case EBML_ID_CHAPTERS:
     1020
     1021                                                                while ($this->getEBMLelement($subelement, $element_data['end'])) {
     1022                                                                        switch ($subelement['id']) {
     1023
     1024                                                                                case EBML_ID_EDITIONENTRY:
     1025                                                                                        $editionentry_entry = array();
     1026
     1027                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) {
     1028                                                                                                switch ($sub_subelement['id']) {
     1029
     1030                                                                                                        case EBML_ID_EDITIONUID:
     1031                                                                                                                $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     1032                                                                                                                break;
     1033
     1034                                                                                                        case EBML_ID_EDITIONFLAGHIDDEN:
     1035                                                                                                        case EBML_ID_EDITIONFLAGDEFAULT:
     1036                                                                                                        case EBML_ID_EDITIONFLAGORDERED:
     1037                                                                                                                $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']);
     1038                                                                                                                break;
     1039
     1040                                                                                                        case EBML_ID_CHAPTERATOM:
     1041                                                                                                                $chapteratom_entry = array();
     1042
     1043                                                                                                                while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) {
     1044                                                                                                                        switch ($sub_sub_subelement['id']) {
     1045
     1046                                                                                                                                case EBML_ID_CHAPTERSEGMENTUID:
     1047                                                                                                                                case EBML_ID_CHAPTERSEGMENTEDITIONUID:
     1048                                                                                                                                        $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data'];
     1049                                                                                                                                        break;
     1050
     1051                                                                                                                                case EBML_ID_CHAPTERFLAGENABLED:
     1052                                                                                                                                case EBML_ID_CHAPTERFLAGHIDDEN:
     1053                                                                                                                                        $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     1054                                                                                                                                        break;
     1055
     1056                                                                                                                                case EBML_ID_CHAPTERUID:
     1057                                                                                                                                case EBML_ID_CHAPTERTIMESTART:
     1058                                                                                                                                case EBML_ID_CHAPTERTIMEEND:
     1059                                                                                                                                        $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
     1060                                                                                                                                        break;
     1061
     1062                                                                                                                                case EBML_ID_CHAPTERTRACK:
     1063                                                                                                                                        $chaptertrack_entry = array();
     1064
     1065                                                                                                                                        while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
     1066                                                                                                                                                switch ($sub_sub_sub_subelement['id']) {
     1067
     1068                                                                                                                                                        case EBML_ID_CHAPTERTRACKNUMBER:
     1069                                                                                                                                                                $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
     1070                                                                                                                                                                break;
     1071
     1072                                                                                                                                                        default:
     1073                                                                                                                                                                $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement);
     1074                                                                                                                                                }
     1075                                                                                                                                        }
     1076                                                                                                                                        $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry;
     1077                                                                                                                                        break;
     1078
     1079                                                                                                                                case EBML_ID_CHAPTERDISPLAY:
     1080                                                                                                                                        $chapterdisplay_entry = array();
     1081
     1082                                                                                                                                        while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
     1083                                                                                                                                                switch ($sub_sub_sub_subelement['id']) {
     1084
     1085                                                                                                                                                        case EBML_ID_CHAPSTRING:
     1086                                                                                                                                                        case EBML_ID_CHAPLANGUAGE:
     1087                                                                                                                                                        case EBML_ID_CHAPCOUNTRY:
     1088                                                                                                                                                                $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
     1089                                                                                                                                                                break;
     1090
     1091                                                                                                                                                        default:
     1092                                                                                                                                                                $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement);
     1093                                                                                                                                                }
     1094                                                                                                                                        }
     1095                                                                                                                                        $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry;
     1096                                                                                                                                        break;
     1097
     1098                                                                                                                                default:
     1099                                                                                                                                        $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement);
     1100                                                                                                                        }
     1101                                                                                                                }
     1102                                                                                                                $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry;
     1103                                                                                                                break;
     1104
     1105                                                                                                        default:
     1106                                                                                                                $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement);
     1107                                                                                                }
     1108                                                                                        }
     1109                                                                                        $info['matroska']['chapters'][] = $editionentry_entry;
     1110                                                                                        break;
     1111
     1112                                                                                default:
     1113                                                                                        $this->unhandledElement('chapters', __LINE__, $subelement);
     1114                                                                        }
     1115                                                                }
     1116                                                                break;
     1117
     1118                                                        case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure.
     1119                                                                $cluster_entry = array();
     1120
     1121                                                                while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) {
     1122                                                                        switch ($subelement['id']) {
     1123
     1124                                                                                case EBML_ID_CLUSTERTIMECODE:
     1125                                                                                case EBML_ID_CLUSTERPOSITION:
     1126                                                                                case EBML_ID_CLUSTERPREVSIZE:
     1127                                                                                        $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
     1128                                                                                        break;
     1129
     1130                                                                                case EBML_ID_CLUSTERSILENTTRACKS:
     1131                                                                                        $cluster_silent_tracks = array();
     1132
     1133                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
     1134                                                                                                switch ($sub_subelement['id']) {
     1135
     1136                                                                                                        case EBML_ID_CLUSTERSILENTTRACKNUMBER:
     1137                                                                                                                $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     1138                                                                                                                break;
     1139
     1140                                                                                                        default:
     1141                                                                                                                $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement);
     1142                                                                                                }
     1143                                                                                        }
     1144                                                                                        $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks;
     1145                                                                                        break;
     1146
     1147                                                                                case EBML_ID_CLUSTERBLOCKGROUP:
     1148                                                                                        $cluster_block_group = array('offset' => $this->current_offset);
     1149
     1150                                                                                        while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) {
     1151                                                                                                switch ($sub_subelement['id']) {
     1152
     1153                                                                                                        case EBML_ID_CLUSTERBLOCK:
     1154                                                                                                                $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info);
     1155                                                                                                                break;
     1156
     1157                                                                                                        case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int
     1158                                                                                                        case EBML_ID_CLUSTERBLOCKDURATION:     // unsigned-int
     1159                                                                                                                $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
     1160                                                                                                                break;
     1161
     1162                                                                                                        case EBML_ID_CLUSTERREFERENCEBLOCK:    // signed-int
     1163                                                                                                                $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true);
     1164                                                                                                                break;
     1165
     1166                                                                                                        case EBML_ID_CLUSTERCODECSTATE:
     1167                                                                                                                $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
     1168                                                                                                                break;
     1169
     1170                                                                                                        default:
     1171                                                                                                                $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement);
     1172                                                                                                }
     1173                                                                                        }
     1174                                                                                        $cluster_entry[$subelement['id_name']][] = $cluster_block_group;
     1175                                                                                        break;
     1176
     1177                                                                                case EBML_ID_CLUSTERSIMPLEBLOCK:
     1178                                                                                        $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info);
     1179                                                                                        break;
     1180
     1181                                                                                default:
     1182                                                                                        $this->unhandledElement('cluster', __LINE__, $subelement);
     1183                                                                        }
     1184                                                                        $this->current_offset = $subelement['end'];
     1185                                                                }
     1186                                                                if (!self::$hide_clusters) {
     1187                                                                        $info['matroska']['cluster'][] = $cluster_entry;
     1188                                                                }
     1189
     1190                                                                // check to see if all the data we need exists already, if so, break out of the loop
     1191                                                                if (!self::$parse_whole_file) {
     1192                                                                        if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
     1193                                                                                if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
     1194                                                                                        if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) {
     1195                                                                                                return;
     1196                                                                                        }
     1197                                                                                }
     1198                                                                        }
     1199                                                                }
     1200                                                                break;
     1201
     1202                                                        default:
     1203                                                                $this->unhandledElement('segment', __LINE__, $element_data);
     1204                                                }
     1205                                        }
     1206                                        break;
     1207
     1208                                default:
     1209                                        $this->unhandledElement('root', __LINE__, $top_element);
     1210                        }
     1211                }
     1212    }
     1213
     1214        private function EnsureBufferHasEnoughData($min_data=1024) {
     1215                if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) {
     1216                        $read_bytes = max($min_data, $this->getid3->fread_buffer_size());
     1217
     1218                        try {
     1219                                $this->fseek($this->current_offset);
     1220                                $this->EBMLbuffer_offset = $this->current_offset;
     1221                                $this->EBMLbuffer        = $this->fread($read_bytes);
     1222                                $this->EBMLbuffer_length = strlen($this->EBMLbuffer);
     1223                        } catch (getid3_exception $e) {
     1224                                $this->warning('EBML parser: '.$e->getMessage());
     1225                                return false;
     1226                        }
     1227
     1228                        if ($this->EBMLbuffer_length == 0 && $this->feof()) {
     1229                                return $this->error('EBML parser: ran out of file at offset '.$this->current_offset);
     1230                        }
     1231                }
     1232                return true;
     1233        }
     1234
     1235        private function readEBMLint() {
     1236                $actual_offset = $this->current_offset - $this->EBMLbuffer_offset;
     1237
     1238                // get length of integer
     1239                $first_byte_int = ord($this->EBMLbuffer[$actual_offset]);
     1240                if       (0x80 & $first_byte_int) {
     1241                        $length = 1;
     1242                } elseif (0x40 & $first_byte_int) {
     1243                        $length = 2;
     1244                } elseif (0x20 & $first_byte_int) {
     1245                        $length = 3;
     1246                } elseif (0x10 & $first_byte_int) {
     1247                        $length = 4;
     1248                } elseif (0x08 & $first_byte_int) {
     1249                        $length = 5;
     1250                } elseif (0x04 & $first_byte_int) {
     1251                        $length = 6;
     1252                } elseif (0x02 & $first_byte_int) {
     1253                        $length = 7;
     1254                } elseif (0x01 & $first_byte_int) {
     1255                        $length = 8;
     1256                } else {
     1257                        throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset);
     1258                }
     1259
     1260                // read
     1261                $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length));
     1262                $this->current_offset += $length;
     1263
     1264                return $int_value;
     1265        }
     1266
     1267        private function readEBMLelementData($length, $check_buffer=false) {
     1268                if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) {
     1269                        return false;
     1270                }
     1271                $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length);
     1272                $this->current_offset += $length;
     1273                return $data;
     1274        }
     1275
     1276        private function getEBMLelement(&$element, $parent_end, $get_data=false) {
     1277                if ($this->current_offset >= $parent_end) {
     1278                        return false;
     1279                }
     1280
     1281                if (!$this->EnsureBufferHasEnoughData()) {
     1282                        $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information
     1283                        return false;
     1284                }
     1285
     1286                $element = array();
     1287
     1288                // set offset
     1289                $element['offset'] = $this->current_offset;
     1290
     1291                // get ID
     1292                $element['id'] = $this->readEBMLint();
     1293
     1294                // get name
     1295                $element['id_name'] = self::EBMLidName($element['id']);
     1296
     1297                // get length
     1298                $element['length'] = $this->readEBMLint();
     1299
     1300                // get end offset
     1301                $element['end'] = $this->current_offset + $element['length'];
     1302
     1303                // get raw data
     1304                $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id']));
     1305                if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) {
     1306                        $element['data'] = $this->readEBMLelementData($element['length'], $element);
     1307                }
     1308
     1309                return true;
     1310        }
     1311
     1312        private function unhandledElement($type, $line, $element) {
     1313                // warn only about unknown and missed elements, not about unuseful
     1314                if (!in_array($element['id'], $this->unuseful_elements)) {
     1315                        $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']);
     1316                }
     1317
     1318                // increase offset for unparsed elements
     1319                if (!isset($element['data'])) {
     1320                        $this->current_offset = $element['end'];
     1321                }
     1322        }
     1323
     1324        private function ExtractCommentsSimpleTag($SimpleTagArray) {
     1325                if (!empty($SimpleTagArray['SimpleTag'])) {
     1326                        foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) {
     1327                                if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) {
     1328                                        $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString'];
     1329                                }
     1330                                if (!empty($SimpleTagData['SimpleTag'])) {
     1331                                        $this->ExtractCommentsSimpleTag($SimpleTagData);
     1332                                }
     1333                        }
     1334                }
     1335
     1336                return true;
     1337        }
     1338
     1339        private function HandleEMBLSimpleTag($parent_end) {
     1340                $simpletag_entry = array();
     1341
     1342                while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) {
     1343                        switch ($element['id']) {
     1344
     1345                                case EBML_ID_TAGNAME:
     1346                                case EBML_ID_TAGLANGUAGE:
     1347                                case EBML_ID_TAGSTRING:
     1348                                case EBML_ID_TAGBINARY:
     1349                                        $simpletag_entry[$element['id_name']] = $element['data'];
     1350                                        break;
     1351
     1352                                case EBML_ID_SIMPLETAG:
     1353                                        $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']);
     1354                                        break;
     1355
     1356                                case EBML_ID_TAGDEFAULT:
     1357                                        $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']);
     1358                                        break;
     1359
     1360                                default:
     1361                                        $this->unhandledElement('tag.simpletag', __LINE__, $element);
     1362                        }
     1363                }
     1364
     1365                return $simpletag_entry;
     1366        }
     1367
     1368        private function HandleEMBLClusterBlock($element, $block_type, &$info) {
     1369                // http://www.matroska.org/technical/specs/index.html#block_structure
     1370                // http://www.matroska.org/technical/specs/index.html#simpleblock_structure
     1371
     1372                $block_data = array();
     1373                $block_data['tracknumber'] = $this->readEBMLint();
     1374                $block_data['timecode']    = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true);
     1375                $block_data['flags_raw']   = getid3_lib::BigEndian2Int($this->readEBMLelementData(1));
     1376
     1377                if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) {
     1378                        $block_data['flags']['keyframe']  = (($block_data['flags_raw'] & 0x80) >> 7);
     1379                        //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4);
     1380                }
     1381                else {
     1382                        //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4);
     1383                }
     1384                $block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3);
     1385                $block_data['flags']['lacing']    =       (($block_data['flags_raw'] & 0x06) >> 1);  // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing
     1386                if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) {
     1387                        $block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01));
     1388                }
     1389                else {
     1390                        //$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0);
     1391                }
     1392                $block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']);
     1393
     1394        // Lace (when lacing bit is set)
     1395                if ($block_data['flags']['lacing'] > 0) {
     1396                        $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8)
     1397                        if ($block_data['flags']['lacing'] != 0x02) {
     1398                                for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace).
     1399                                        if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing
     1400                                                $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing.
     1401                                        }
     1402                                        else { // Xiph lacing
     1403                                                $block_data['lace_frames_size'][$i] = 0;
     1404                                                do {
     1405                                                        $size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1));
     1406                                                        $block_data['lace_frames_size'][$i] += $size;
     1407                                                }
     1408                                                while ($size == 255);
     1409                                        }
     1410                                }
     1411                                if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly
     1412                                        $block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']);
     1413                                }
     1414                        }
     1415                }
     1416
     1417                if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) {
     1418                        $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset;
     1419                        $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset;
     1420                        //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0;
     1421                }
     1422                //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'];
     1423                //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration']      = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000);
     1424
     1425                // set offset manually
     1426                $this->current_offset = $element['end'];
     1427
     1428                return $block_data;
     1429        }
     1430
     1431        private static function EBML2Int($EBMLstring) {
     1432                // http://matroska.org/specs/
     1433
     1434                // Element ID coded with an UTF-8 like system:
     1435                // 1xxx xxxx                                  - Class A IDs (2^7 -2 possible values) (base 0x8X)
     1436                // 01xx xxxx  xxxx xxxx                       - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX)
     1437                // 001x xxxx  xxxx xxxx  xxxx xxxx            - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX)
     1438                // 0001 xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX)
     1439                // Values with all x at 0 and 1 are reserved (hence the -2).
     1440
     1441                // Data size, in octets, is also coded with an UTF-8 like system :
     1442                // 1xxx xxxx                                                                              - value 0 to  2^7-2
     1443                // 01xx xxxx  xxxx xxxx                                                                   - value 0 to 2^14-2
     1444                // 001x xxxx  xxxx xxxx  xxxx xxxx                                                        - value 0 to 2^21-2
     1445                // 0001 xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                                             - value 0 to 2^28-2
     1446                // 0000 1xxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                                  - value 0 to 2^35-2
     1447                // 0000 01xx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                       - value 0 to 2^42-2
     1448                // 0000 001x  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx            - value 0 to 2^49-2
     1449                // 0000 0001  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx - value 0 to 2^56-2
     1450
     1451                $first_byte_int = ord($EBMLstring[0]);
     1452                if (0x80 & $first_byte_int) {
     1453                        $EBMLstring[0] = chr($first_byte_int & 0x7F);
     1454                } elseif (0x40 & $first_byte_int) {
     1455                        $EBMLstring[0] = chr($first_byte_int & 0x3F);
     1456                } elseif (0x20 & $first_byte_int) {
     1457                        $EBMLstring[0] = chr($first_byte_int & 0x1F);
     1458                } elseif (0x10 & $first_byte_int) {
     1459                        $EBMLstring[0] = chr($first_byte_int & 0x0F);
     1460                } elseif (0x08 & $first_byte_int) {
     1461                        $EBMLstring[0] = chr($first_byte_int & 0x07);
     1462                } elseif (0x04 & $first_byte_int) {
     1463                        $EBMLstring[0] = chr($first_byte_int & 0x03);
     1464                } elseif (0x02 & $first_byte_int) {
     1465                        $EBMLstring[0] = chr($first_byte_int & 0x01);
     1466                } elseif (0x01 & $first_byte_int) {
     1467                        $EBMLstring[0] = chr($first_byte_int & 0x00);
     1468                }
     1469
     1470                return getid3_lib::BigEndian2Int($EBMLstring);
     1471        }
     1472
     1473        private static function EBMLdate2unix($EBMLdatestamp) {
     1474                // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC)
     1475                // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC
     1476                return round(($EBMLdatestamp / 1000000000) + 978307200);
     1477        }
     1478
     1479        public static function TargetTypeValue($target_type) {
     1480                // http://www.matroska.org/technical/specs/tagging/index.html
     1481                static $TargetTypeValue = array();
     1482                if (empty($TargetTypeValue)) {
     1483                        $TargetTypeValue[10] = 'A: ~ V:shot';                                           // the lowest hierarchy found in music or movies
     1484                        $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene';                    // corresponds to parts of a track for audio (like a movement)
     1485                        $TargetTypeValue[30] = 'A:track/song ~ V:chapter';                              // the common parts of an album or a movie
     1486                        $TargetTypeValue[40] = 'A:part/session ~ V:part/session';                       // when an album or episode has different logical parts
     1487                        $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert';       // the most common grouping level of music and video (equals to an episode for TV series)
     1488                        $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume';  // a list of lower levels grouped together
     1489                        $TargetTypeValue[70] = 'A:collection ~ V:collection';                           // the high hierarchy consisting of many different lower items
     1490                }
     1491                return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type);
     1492        }
     1493
     1494        public static function BlockLacingType($lacingtype) {
     1495                // http://matroska.org/technical/specs/index.html#block_structure
     1496                static $BlockLacingType = array();
     1497                if (empty($BlockLacingType)) {
     1498                        $BlockLacingType[0x00] = 'no lacing';
     1499                        $BlockLacingType[0x01] = 'Xiph lacing';
     1500                        $BlockLacingType[0x02] = 'fixed-size lacing';
     1501                        $BlockLacingType[0x03] = 'EBML lacing';
     1502                }
     1503                return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype);
     1504        }
     1505
     1506        public static function CodecIDtoCommonName($codecid) {
     1507                // http://www.matroska.org/technical/specs/codecid/index.html
     1508                static $CodecIDlist = array();
     1509                if (empty($CodecIDlist)) {
     1510                        $CodecIDlist['A_AAC']            = 'aac';
     1511                        $CodecIDlist['A_AAC/MPEG2/LC']   = 'aac';
     1512                        $CodecIDlist['A_AC3']            = 'ac3';
     1513                        $CodecIDlist['A_DTS']            = 'dts';
     1514                        $CodecIDlist['A_FLAC']           = 'flac';
     1515                        $CodecIDlist['A_MPEG/L1']        = 'mp1';
     1516                        $CodecIDlist['A_MPEG/L2']        = 'mp2';
     1517                        $CodecIDlist['A_MPEG/L3']        = 'mp3';
     1518                        $CodecIDlist['A_PCM/INT/LIT']    = 'pcm';       // PCM Integer Little Endian
     1519                        $CodecIDlist['A_PCM/INT/BIG']    = 'pcm';       // PCM Integer Big Endian
     1520                        $CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music
     1521                        $CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2
     1522                        $CodecIDlist['A_VORBIS']         = 'vorbis';
     1523                        $CodecIDlist['V_MPEG1']          = 'mpeg';
     1524                        $CodecIDlist['V_THEORA']         = 'theora';
     1525                        $CodecIDlist['V_REAL/RV40']      = 'real';
     1526                        $CodecIDlist['V_REAL/RV10']      = 'real';
     1527                        $CodecIDlist['V_REAL/RV20']      = 'real';
     1528                        $CodecIDlist['V_REAL/RV30']      = 'real';
     1529                        $CodecIDlist['V_QUICKTIME']      = 'quicktime'; // Quicktime
     1530                        $CodecIDlist['V_MPEG4/ISO/AP']   = 'mpeg4';
     1531                        $CodecIDlist['V_MPEG4/ISO/ASP']  = 'mpeg4';
     1532                        $CodecIDlist['V_MPEG4/ISO/AVC']  = 'h264';
     1533                        $CodecIDlist['V_MPEG4/ISO/SP']   = 'mpeg4';
     1534                        $CodecIDlist['V_VP8']            = 'vp8';
     1535                        $CodecIDlist['V_MS/VFW/FOURCC']  = 'riff';
     1536                        $CodecIDlist['A_MS/ACM']         = 'riff';
     1537                }
     1538                return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid);
     1539        }
     1540
     1541        private static function EBMLidName($value) {
     1542                static $EBMLidList = array();
     1543                if (empty($EBMLidList)) {
     1544                        $EBMLidList[EBML_ID_ASPECTRATIOTYPE]            = 'AspectRatioType';
     1545                        $EBMLidList[EBML_ID_ATTACHEDFILE]               = 'AttachedFile';
     1546                        $EBMLidList[EBML_ID_ATTACHMENTLINK]             = 'AttachmentLink';
     1547                        $EBMLidList[EBML_ID_ATTACHMENTS]                = 'Attachments';
     1548                        $EBMLidList[EBML_ID_AUDIO]                      = 'Audio';
     1549                        $EBMLidList[EBML_ID_BITDEPTH]                   = 'BitDepth';
     1550                        $EBMLidList[EBML_ID_CHANNELPOSITIONS]           = 'ChannelPositions';
     1551                        $EBMLidList[EBML_ID_CHANNELS]                   = 'Channels';
     1552                        $EBMLidList[EBML_ID_CHAPCOUNTRY]                = 'ChapCountry';
     1553                        $EBMLidList[EBML_ID_CHAPLANGUAGE]               = 'ChapLanguage';
     1554                        $EBMLidList[EBML_ID_CHAPPROCESS]                = 'ChapProcess';
     1555                        $EBMLidList[EBML_ID_CHAPPROCESSCODECID]         = 'ChapProcessCodecID';
     1556                        $EBMLidList[EBML_ID_CHAPPROCESSCOMMAND]         = 'ChapProcessCommand';
     1557                        $EBMLidList[EBML_ID_CHAPPROCESSDATA]            = 'ChapProcessData';
     1558                        $EBMLidList[EBML_ID_CHAPPROCESSPRIVATE]         = 'ChapProcessPrivate';
     1559                        $EBMLidList[EBML_ID_CHAPPROCESSTIME]            = 'ChapProcessTime';
     1560                        $EBMLidList[EBML_ID_CHAPSTRING]                 = 'ChapString';
     1561                        $EBMLidList[EBML_ID_CHAPTERATOM]                = 'ChapterAtom';
     1562                        $EBMLidList[EBML_ID_CHAPTERDISPLAY]             = 'ChapterDisplay';
     1563                        $EBMLidList[EBML_ID_CHAPTERFLAGENABLED]         = 'ChapterFlagEnabled';
     1564                        $EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN]          = 'ChapterFlagHidden';
     1565                        $EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV]       = 'ChapterPhysicalEquiv';
     1566                        $EBMLidList[EBML_ID_CHAPTERS]                   = 'Chapters';
     1567                        $EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID]   = 'ChapterSegmentEditionUID';
     1568                        $EBMLidList[EBML_ID_CHAPTERSEGMENTUID]          = 'ChapterSegmentUID';
     1569                        $EBMLidList[EBML_ID_CHAPTERTIMEEND]             = 'ChapterTimeEnd';
     1570                        $EBMLidList[EBML_ID_CHAPTERTIMESTART]           = 'ChapterTimeStart';
     1571                        $EBMLidList[EBML_ID_CHAPTERTRACK]               = 'ChapterTrack';
     1572                        $EBMLidList[EBML_ID_CHAPTERTRACKNUMBER]         = 'ChapterTrackNumber';
     1573                        $EBMLidList[EBML_ID_CHAPTERTRANSLATE]           = 'ChapterTranslate';
     1574                        $EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC]      = 'ChapterTranslateCodec';
     1575                        $EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID';
     1576                        $EBMLidList[EBML_ID_CHAPTERTRANSLATEID]         = 'ChapterTranslateID';
     1577                        $EBMLidList[EBML_ID_CHAPTERUID]                 = 'ChapterUID';
     1578                        $EBMLidList[EBML_ID_CLUSTER]                    = 'Cluster';
     1579                        $EBMLidList[EBML_ID_CLUSTERBLOCK]               = 'ClusterBlock';
     1580                        $EBMLidList[EBML_ID_CLUSTERBLOCKADDID]          = 'ClusterBlockAddID';
     1581                        $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL]     = 'ClusterBlockAdditional';
     1582                        $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID]     = 'ClusterBlockAdditionID';
     1583                        $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS]      = 'ClusterBlockAdditions';
     1584                        $EBMLidList[EBML_ID_CLUSTERBLOCKDURATION]       = 'ClusterBlockDuration';
     1585                        $EBMLidList[EBML_ID_CLUSTERBLOCKGROUP]          = 'ClusterBlockGroup';
     1586                        $EBMLidList[EBML_ID_CLUSTERBLOCKMORE]           = 'ClusterBlockMore';
     1587                        $EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL]        = 'ClusterBlockVirtual';
     1588                        $EBMLidList[EBML_ID_CLUSTERCODECSTATE]          = 'ClusterCodecState';
     1589                        $EBMLidList[EBML_ID_CLUSTERDELAY]               = 'ClusterDelay';
     1590                        $EBMLidList[EBML_ID_CLUSTERDURATION]            = 'ClusterDuration';
     1591                        $EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK]      = 'ClusterEncryptedBlock';
     1592                        $EBMLidList[EBML_ID_CLUSTERFRAMENUMBER]         = 'ClusterFrameNumber';
     1593                        $EBMLidList[EBML_ID_CLUSTERLACENUMBER]          = 'ClusterLaceNumber';
     1594                        $EBMLidList[EBML_ID_CLUSTERPOSITION]            = 'ClusterPosition';
     1595                        $EBMLidList[EBML_ID_CLUSTERPREVSIZE]            = 'ClusterPrevSize';
     1596                        $EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK]      = 'ClusterReferenceBlock';
     1597                        $EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY]   = 'ClusterReferencePriority';
     1598                        $EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL]    = 'ClusterReferenceVirtual';
     1599                        $EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER]   = 'ClusterSilentTrackNumber';
     1600                        $EBMLidList[EBML_ID_CLUSTERSILENTTRACKS]        = 'ClusterSilentTracks';
     1601                        $EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK]         = 'ClusterSimpleBlock';
     1602                        $EBMLidList[EBML_ID_CLUSTERTIMECODE]            = 'ClusterTimecode';
     1603                        $EBMLidList[EBML_ID_CLUSTERTIMESLICE]           = 'ClusterTimeSlice';
     1604                        $EBMLidList[EBML_ID_CODECDECODEALL]             = 'CodecDecodeAll';
     1605                        $EBMLidList[EBML_ID_CODECDOWNLOADURL]           = 'CodecDownloadURL';
     1606                        $EBMLidList[EBML_ID_CODECID]                    = 'CodecID';
     1607                        $EBMLidList[EBML_ID_CODECINFOURL]               = 'CodecInfoURL';
     1608                        $EBMLidList[EBML_ID_CODECNAME]                  = 'CodecName';
     1609                        $EBMLidList[EBML_ID_CODECPRIVATE]               = 'CodecPrivate';
     1610                        $EBMLidList[EBML_ID_CODECSETTINGS]              = 'CodecSettings';
     1611                        $EBMLidList[EBML_ID_COLOURSPACE]                = 'ColourSpace';
     1612                        $EBMLidList[EBML_ID_CONTENTCOMPALGO]            = 'ContentCompAlgo';
     1613                        $EBMLidList[EBML_ID_CONTENTCOMPRESSION]         = 'ContentCompression';
     1614                        $EBMLidList[EBML_ID_CONTENTCOMPSETTINGS]        = 'ContentCompSettings';
     1615                        $EBMLidList[EBML_ID_CONTENTENCALGO]             = 'ContentEncAlgo';
     1616                        $EBMLidList[EBML_ID_CONTENTENCKEYID]            = 'ContentEncKeyID';
     1617                        $EBMLidList[EBML_ID_CONTENTENCODING]            = 'ContentEncoding';
     1618                        $EBMLidList[EBML_ID_CONTENTENCODINGORDER]       = 'ContentEncodingOrder';
     1619                        $EBMLidList[EBML_ID_CONTENTENCODINGS]           = 'ContentEncodings';
     1620                        $EBMLidList[EBML_ID_CONTENTENCODINGSCOPE]       = 'ContentEncodingScope';
     1621                        $EBMLidList[EBML_ID_CONTENTENCODINGTYPE]        = 'ContentEncodingType';
     1622                        $EBMLidList[EBML_ID_CONTENTENCRYPTION]          = 'ContentEncryption';
     1623                        $EBMLidList[EBML_ID_CONTENTSIGALGO]             = 'ContentSigAlgo';
     1624                        $EBMLidList[EBML_ID_CONTENTSIGHASHALGO]         = 'ContentSigHashAlgo';
     1625                        $EBMLidList[EBML_ID_CONTENTSIGKEYID]            = 'ContentSigKeyID';
     1626                        $EBMLidList[EBML_ID_CONTENTSIGNATURE]           = 'ContentSignature';
     1627                        $EBMLidList[EBML_ID_CRC32]                      = 'CRC32';
     1628                        $EBMLidList[EBML_ID_CUEBLOCKNUMBER]             = 'CueBlockNumber';
     1629                        $EBMLidList[EBML_ID_CUECLUSTERPOSITION]         = 'CueClusterPosition';
     1630                        $EBMLidList[EBML_ID_CUECODECSTATE]              = 'CueCodecState';
     1631                        $EBMLidList[EBML_ID_CUEPOINT]                   = 'CuePoint';
     1632                        $EBMLidList[EBML_ID_CUEREFCLUSTER]              = 'CueRefCluster';
     1633                        $EBMLidList[EBML_ID_CUEREFCODECSTATE]           = 'CueRefCodecState';
     1634                        $EBMLidList[EBML_ID_CUEREFERENCE]               = 'CueReference';
     1635                        $EBMLidList[EBML_ID_CUEREFNUMBER]               = 'CueRefNumber';
     1636                        $EBMLidList[EBML_ID_CUEREFTIME]                 = 'CueRefTime';
     1637                        $EBMLidList[EBML_ID_CUES]                       = 'Cues';
     1638                        $EBMLidList[EBML_ID_CUETIME]                    = 'CueTime';
     1639                        $EBMLidList[EBML_ID_CUETRACK]                   = 'CueTrack';
     1640                        $EBMLidList[EBML_ID_CUETRACKPOSITIONS]          = 'CueTrackPositions';
     1641                        $EBMLidList[EBML_ID_DATEUTC]                    = 'DateUTC';
     1642                        $EBMLidList[EBML_ID_DEFAULTDURATION]            = 'DefaultDuration';
     1643                        $EBMLidList[EBML_ID_DISPLAYHEIGHT]              = 'DisplayHeight';
     1644                        $EBMLidList[EBML_ID_DISPLAYUNIT]                = 'DisplayUnit';
     1645                        $EBMLidList[EBML_ID_DISPLAYWIDTH]               = 'DisplayWidth';
     1646                        $EBMLidList[EBML_ID_DOCTYPE]                    = 'DocType';
     1647                        $EBMLidList[EBML_ID_DOCTYPEREADVERSION]         = 'DocTypeReadVersion';
     1648                        $EBMLidList[EBML_ID_DOCTYPEVERSION]             = 'DocTypeVersion';
     1649                        $EBMLidList[EBML_ID_DURATION]                   = 'Duration';
     1650                        $EBMLidList[EBML_ID_EBML]                       = 'EBML';
     1651                        $EBMLidList[EBML_ID_EBMLMAXIDLENGTH]            = 'EBMLMaxIDLength';
     1652                        $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH]          = 'EBMLMaxSizeLength';
     1653                        $EBMLidList[EBML_ID_EBMLREADVERSION]            = 'EBMLReadVersion';
     1654                        $EBMLidList[EBML_ID_EBMLVERSION]                = 'EBMLVersion';
     1655                        $EBMLidList[EBML_ID_EDITIONENTRY]               = 'EditionEntry';
     1656                        $EBMLidList[EBML_ID_EDITIONFLAGDEFAULT]         = 'EditionFlagDefault';
     1657                        $EBMLidList[EBML_ID_EDITIONFLAGHIDDEN]          = 'EditionFlagHidden';
     1658                        $EBMLidList[EBML_ID_EDITIONFLAGORDERED]         = 'EditionFlagOrdered';
     1659                        $EBMLidList[EBML_ID_EDITIONUID]                 = 'EditionUID';
     1660                        $EBMLidList[EBML_ID_FILEDATA]                   = 'FileData';
     1661                        $EBMLidList[EBML_ID_FILEDESCRIPTION]            = 'FileDescription';
     1662                        $EBMLidList[EBML_ID_FILEMIMETYPE]               = 'FileMimeType';
     1663                        $EBMLidList[EBML_ID_FILENAME]                   = 'FileName';
     1664                        $EBMLidList[EBML_ID_FILEREFERRAL]               = 'FileReferral';
     1665                        $EBMLidList[EBML_ID_FILEUID]                    = 'FileUID';
     1666                        $EBMLidList[EBML_ID_FLAGDEFAULT]                = 'FlagDefault';
     1667                        $EBMLidList[EBML_ID_FLAGENABLED]                = 'FlagEnabled';
     1668                        $EBMLidList[EBML_ID_FLAGFORCED]                 = 'FlagForced';
     1669                        $EBMLidList[EBML_ID_FLAGINTERLACED]             = 'FlagInterlaced';
     1670                        $EBMLidList[EBML_ID_FLAGLACING]                 = 'FlagLacing';
     1671                        $EBMLidList[EBML_ID_GAMMAVALUE]                 = 'GammaValue';
     1672                        $EBMLidList[EBML_ID_INFO]                       = 'Info';
     1673                        $EBMLidList[EBML_ID_LANGUAGE]                   = 'Language';
     1674                        $EBMLidList[EBML_ID_MAXBLOCKADDITIONID]         = 'MaxBlockAdditionID';
     1675                        $EBMLidList[EBML_ID_MAXCACHE]                   = 'MaxCache';
     1676                        $EBMLidList[EBML_ID_MINCACHE]                   = 'MinCache';
     1677                        $EBMLidList[EBML_ID_MUXINGAPP]                  = 'MuxingApp';
     1678                        $EBMLidList[EBML_ID_NAME]                       = 'Name';
     1679                        $EBMLidList[EBML_ID_NEXTFILENAME]               = 'NextFilename';
     1680                        $EBMLidList[EBML_ID_NEXTUID]                    = 'NextUID';
     1681                        $EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY]    = 'OutputSamplingFrequency';
     1682                        $EBMLidList[EBML_ID_PIXELCROPBOTTOM]            = 'PixelCropBottom';
     1683                        $EBMLidList[EBML_ID_PIXELCROPLEFT]              = 'PixelCropLeft';
     1684                        $EBMLidList[EBML_ID_PIXELCROPRIGHT]             = 'PixelCropRight';
     1685                        $EBMLidList[EBML_ID_PIXELCROPTOP]               = 'PixelCropTop';
     1686                        $EBMLidList[EBML_ID_PIXELHEIGHT]                = 'PixelHeight';
     1687                        $EBMLidList[EBML_ID_PIXELWIDTH]                 = 'PixelWidth';
     1688                        $EBMLidList[EBML_ID_PREVFILENAME]               = 'PrevFilename';
     1689                        $EBMLidList[EBML_ID_PREVUID]                    = 'PrevUID';
     1690                        $EBMLidList[EBML_ID_SAMPLINGFREQUENCY]          = 'SamplingFrequency';
     1691                        $EBMLidList[EBML_ID_SEEK]                       = 'Seek';
     1692                        $EBMLidList[EBML_ID_SEEKHEAD]                   = 'SeekHead';
     1693                        $EBMLidList[EBML_ID_SEEKID]                     = 'SeekID';
     1694                        $EBMLidList[EBML_ID_SEEKPOSITION]               = 'SeekPosition';
     1695                        $EBMLidList[EBML_ID_SEGMENT]                    = 'Segment';
     1696                        $EBMLidList[EBML_ID_SEGMENTFAMILY]              = 'SegmentFamily';
     1697                        $EBMLidList[EBML_ID_SEGMENTFILENAME]            = 'SegmentFilename';
     1698                        $EBMLidList[EBML_ID_SEGMENTUID]                 = 'SegmentUID';
     1699                        $EBMLidList[EBML_ID_SIMPLETAG]                  = 'SimpleTag';
     1700                        $EBMLidList[EBML_ID_CLUSTERSLICES]              = 'ClusterSlices';
     1701                        $EBMLidList[EBML_ID_STEREOMODE]                 = 'StereoMode';
     1702                        $EBMLidList[EBML_ID_OLDSTEREOMODE]              = 'OldStereoMode';
     1703                        $EBMLidList[EBML_ID_TAG]                        = 'Tag';
     1704                        $EBMLidList[EBML_ID_TAGATTACHMENTUID]           = 'TagAttachmentUID';
     1705                        $EBMLidList[EBML_ID_TAGBINARY]                  = 'TagBinary';
     1706                        $EBMLidList[EBML_ID_TAGCHAPTERUID]              = 'TagChapterUID';
     1707                        $EBMLidList[EBML_ID_TAGDEFAULT]                 = 'TagDefault';
     1708                        $EBMLidList[EBML_ID_TAGEDITIONUID]              = 'TagEditionUID';
     1709                        $EBMLidList[EBML_ID_TAGLANGUAGE]                = 'TagLanguage';
     1710                        $EBMLidList[EBML_ID_TAGNAME]                    = 'TagName';
     1711                        $EBMLidList[EBML_ID_TAGTRACKUID]                = 'TagTrackUID';
     1712                        $EBMLidList[EBML_ID_TAGS]                       = 'Tags';
     1713                        $EBMLidList[EBML_ID_TAGSTRING]                  = 'TagString';
     1714                        $EBMLidList[EBML_ID_TARGETS]                    = 'Targets';
     1715                        $EBMLidList[EBML_ID_TARGETTYPE]                 = 'TargetType';
     1716                        $EBMLidList[EBML_ID_TARGETTYPEVALUE]            = 'TargetTypeValue';
     1717                        $EBMLidList[EBML_ID_TIMECODESCALE]              = 'TimecodeScale';
     1718                        $EBMLidList[EBML_ID_TITLE]                      = 'Title';
     1719                        $EBMLidList[EBML_ID_TRACKENTRY]                 = 'TrackEntry';
     1720                        $EBMLidList[EBML_ID_TRACKNUMBER]                = 'TrackNumber';
     1721                        $EBMLidList[EBML_ID_TRACKOFFSET]                = 'TrackOffset';
     1722                        $EBMLidList[EBML_ID_TRACKOVERLAY]               = 'TrackOverlay';
     1723                        $EBMLidList[EBML_ID_TRACKS]                     = 'Tracks';
     1724                        $EBMLidList[EBML_ID_TRACKTIMECODESCALE]         = 'TrackTimecodeScale';
     1725                        $EBMLidList[EBML_ID_TRACKTRANSLATE]             = 'TrackTranslate';
     1726                        $EBMLidList[EBML_ID_TRACKTRANSLATECODEC]        = 'TrackTranslateCodec';
     1727                        $EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID]   = 'TrackTranslateEditionUID';
     1728                        $EBMLidList[EBML_ID_TRACKTRANSLATETRACKID]      = 'TrackTranslateTrackID';
     1729                        $EBMLidList[EBML_ID_TRACKTYPE]                  = 'TrackType';
     1730                        $EBMLidList[EBML_ID_TRACKUID]                   = 'TrackUID';
     1731                        $EBMLidList[EBML_ID_VIDEO]                      = 'Video';
     1732                        $EBMLidList[EBML_ID_VOID]                       = 'Void';
     1733                        $EBMLidList[EBML_ID_WRITINGAPP]                 = 'WritingApp';
     1734                }
     1735
     1736                return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value));
     1737        }
     1738
     1739        public static function displayUnit($value) {
     1740                // http://www.matroska.org/technical/specs/index.html#DisplayUnit
     1741                static $units = array(
     1742                        0 => 'pixels',
     1743                        1 => 'centimeters',
     1744                        2 => 'inches',
     1745                        3 => 'Display Aspect Ratio');
     1746
     1747                return (isset($units[$value]) ? $units[$value] : 'unknown');
     1748        }
     1749
     1750        private static function getDefaultStreamInfo($streams)
     1751        {
     1752                foreach (array_reverse($streams) as $stream) {
     1753                        if ($stream['default']) {
     1754                                break;
     1755                        }
     1756                }
     1757
     1758                $unset = array('default', 'name');
     1759                foreach ($unset as $u) {
     1760                        if (isset($stream[$u])) {
     1761                                unset($stream[$u]);
     1762                        }
     1763                }
     1764
     1765                $info = $stream;
     1766                $info['streams'] = $streams;
     1767
     1768                return $info;
     1769        }
     1770
     1771}
  • new file wp-includes/ID3/module.audio-video.quicktime.php

    diff --git wp-includes/ID3/module.audio-video.quicktime.php wp-includes/ID3/module.audio-video.quicktime.php
    new file mode 100644
    index 0000000..db63dab
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio-video.quicktime.php                            //
     11// module for analyzing Quicktime and MP3-in-MP4 files         //
     12// dependencies: module.audio.mp3.php                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
     17
     18class getid3_quicktime extends getid3_handler
     19{
     20
     21        public $ReturnAtomData        = true;
     22        public $ParseAllPossibleAtoms = false;
     23
     24        public function Analyze() {
     25                $info = &$this->getid3->info;
     26
     27                $info['fileformat'] = 'quicktime';
     28                $info['quicktime']['hinting']    = false;
     29                $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present
     30
     31                fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     32
     33                $offset      = 0;
     34                $atomcounter = 0;
     35
     36                while ($offset < $info['avdataend']) {
     37                        if (!getid3_lib::intValueSupported($offset)) {
     38                                $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';
     39                                break;
     40                        }
     41                        fseek($this->getid3->fp, $offset, SEEK_SET);
     42                        $AtomHeader = fread($this->getid3->fp, 8);
     43
     44                        $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
     45                        $atomname = substr($AtomHeader, 4, 4);
     46
     47                        // 64-bit MOV patch by jlegateØktnc*com
     48                        if ($atomsize == 1) {
     49                                $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8));
     50                        }
     51
     52                        $info['quicktime'][$atomname]['name']   = $atomname;
     53                        $info['quicktime'][$atomname]['size']   = $atomsize;
     54                        $info['quicktime'][$atomname]['offset'] = $offset;
     55
     56                        if (($offset + $atomsize) > $info['avdataend']) {
     57                                $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
     58                                return false;
     59                        }
     60
     61                        if ($atomsize == 0) {
     62                                // Furthermore, for historical reasons the list of atoms is optionally
     63                                // terminated by a 32-bit integer set to 0. If you are writing a program
     64                                // to read user data atoms, you should allow for the terminating 0.
     65                                break;
     66                        }
     67                        switch ($atomname) {
     68                                case 'mdat': // Media DATa atom
     69                                        // 'mdat' contains the actual data for the audio/video
     70                                        if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
     71
     72                                                $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8;
     73                                                $OldAVDataEnd         = $info['avdataend'];
     74                                                $info['avdataend']    = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size'];
     75
     76                                                $getid3_temp = new getID3();
     77                                                $getid3_temp->openfile($this->getid3->filename);
     78                                                $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
     79                                                $getid3_temp->info['avdataend']    = $info['avdataend'];
     80                                                $getid3_mp3 = new getid3_mp3($getid3_temp);
     81                                                if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) {
     82                                                        $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
     83                                                        if (!empty($getid3_temp->info['warning'])) {
     84                                                                foreach ($getid3_temp->info['warning'] as $value) {
     85                                                                        $info['warning'][] = $value;
     86                                                                }
     87                                                        }
     88                                                        if (!empty($getid3_temp->info['mpeg'])) {
     89                                                                $info['mpeg'] = $getid3_temp->info['mpeg'];
     90                                                                if (isset($info['mpeg']['audio'])) {
     91                                                                        $info['audio']['dataformat']   = 'mp3';
     92                                                                        $info['audio']['codec']        = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3')));
     93                                                                        $info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
     94                                                                        $info['audio']['channels']     = $info['mpeg']['audio']['channels'];
     95                                                                        $info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
     96                                                                        $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
     97                                                                        $info['bitrate']               = $info['audio']['bitrate'];
     98                                                                }
     99                                                        }
     100                                                }
     101                                                unset($getid3_mp3, $getid3_temp);
     102                                                $info['avdataend'] = $OldAVDataEnd;
     103                                                unset($OldAVDataEnd);
     104
     105                                        }
     106                                        break;
     107
     108                                case 'free': // FREE space atom
     109                                case 'skip': // SKIP atom
     110                                case 'wide': // 64-bit expansion placeholder atom
     111                                        // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
     112                                        break;
     113
     114                                default:
     115                                        $atomHierarchy = array();
     116                                        $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
     117                                        break;
     118                        }
     119
     120                        $offset += $atomsize;
     121                        $atomcounter++;
     122                }
     123
     124                if (!empty($info['avdataend_tmp'])) {
     125                        // this value is assigned to a temp value and then erased because
     126                        // otherwise any atoms beyond the 'mdat' atom would not get parsed
     127                        $info['avdataend'] = $info['avdataend_tmp'];
     128                        unset($info['avdataend_tmp']);
     129                }
     130
     131                if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
     132                        $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
     133                }
     134                if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
     135                        $info['audio']['bitrate'] = $info['bitrate'];
     136                }
     137                if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) {
     138                        foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) {
     139                                $samples_per_second = $samples_count / $info['playtime_seconds'];
     140                                if ($samples_per_second > 240) {
     141                                        // has to be audio samples
     142                                } else {
     143                                        $info['video']['frame_rate'] = $samples_per_second;
     144                                        break;
     145                                }
     146                        }
     147                }
     148                if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {
     149                        $info['fileformat'] = 'mp4';
     150                        $info['mime_type']  = 'audio/mp4';
     151                        unset($info['video']['dataformat']);
     152                }
     153
     154                if (!$this->ReturnAtomData) {
     155                        unset($info['quicktime']['moov']);
     156                }
     157
     158                if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) {
     159                        $info['audio']['dataformat'] = 'quicktime';
     160                }
     161                if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) {
     162                        $info['video']['dataformat'] = 'quicktime';
     163                }
     164
     165                return true;
     166        }
     167
     168        public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
     169                // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
     170
     171                $info = &$this->getid3->info;
     172
     173                $atom_parent = array_pop($atomHierarchy);
     174                array_push($atomHierarchy, $atomname);
     175                $atom_structure['hierarchy'] = implode(' ', $atomHierarchy);
     176                $atom_structure['name']      = $atomname;
     177                $atom_structure['size']      = $atomsize;
     178                $atom_structure['offset']    = $baseoffset;
     179//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'<br>';
     180//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'<br><br>';
     181                switch ($atomname) {
     182                        case 'moov': // MOVie container atom
     183                        case 'trak': // TRAcK container atom
     184                        case 'clip': // CLIPping container atom
     185                        case 'matt': // track MATTe container atom
     186                        case 'edts': // EDiTS container atom
     187                        case 'tref': // Track REFerence container atom
     188                        case 'mdia': // MeDIA container atom
     189                        case 'minf': // Media INFormation container atom
     190                        case 'dinf': // Data INFormation container atom
     191                        case 'udta': // User DaTA container atom
     192                        case 'cmov': // Compressed MOVie container atom
     193                        case 'rmra': // Reference Movie Record Atom
     194                        case 'rmda': // Reference Movie Descriptor Atom
     195                        case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR)
     196                                $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     197                                break;
     198
     199                        case 'ilst': // Item LiST container atom
     200                                $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     201
     202                                // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted
     203                                $allnumericnames = true;
     204                                foreach ($atom_structure['subatoms'] as $subatomarray) {
     205                                        if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
     206                                                $allnumericnames = false;
     207                                                break;
     208                                        }
     209                                }
     210                                if ($allnumericnames) {
     211                                        $newData = array();
     212                                        foreach ($atom_structure['subatoms'] as $subatomarray) {
     213                                                foreach ($subatomarray['subatoms'] as $newData_subatomarray) {
     214                                                        unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']);
     215                                                        $newData[$subatomarray['name']] = $newData_subatomarray;
     216                                                        break;
     217                                                }
     218                                        }
     219                                        $atom_structure['data'] = $newData;
     220                                        unset($atom_structure['subatoms']);
     221                                }
     222                                break;
     223
     224                        case "\x00\x00\x00\x01":
     225                        case "\x00\x00\x00\x02":
     226                        case "\x00\x00\x00\x03":
     227                        case "\x00\x00\x00\x04":
     228                        case "\x00\x00\x00\x05":
     229                                $atomname = getid3_lib::BigEndian2Int($atomname);
     230                                $atom_structure['name'] = $atomname;
     231                                $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     232                                break;
     233
     234                        case 'stbl': // Sample TaBLe container atom
     235                                $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     236                                $isVideo = false;
     237                                $framerate  = 0;
     238                                $framecount = 0;
     239                                foreach ($atom_structure['subatoms'] as $key => $value_array) {
     240                                        if (isset($value_array['sample_description_table'])) {
     241                                                foreach ($value_array['sample_description_table'] as $key2 => $value_array2) {
     242                                                        if (isset($value_array2['data_format'])) {
     243                                                                switch ($value_array2['data_format']) {
     244                                                                        case 'avc1':
     245                                                                        case 'mp4v':
     246                                                                                // video data
     247                                                                                $isVideo = true;
     248                                                                                break;
     249                                                                        case 'mp4a':
     250                                                                                // audio data
     251                                                                                break;
     252                                                                }
     253                                                        }
     254                                                }
     255                                        } elseif (isset($value_array['time_to_sample_table'])) {
     256                                                foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
     257                                                        if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) {
     258                                                                $framerate  = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
     259                                                                $framecount = $value_array2['sample_count'];
     260                                                        }
     261                                                }
     262                                        }
     263                                }
     264                                if ($isVideo && $framerate) {
     265                                        $info['quicktime']['video']['frame_rate'] = $framerate;
     266                                        $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate'];
     267                                }
     268                                if ($isVideo && $framecount) {
     269                                        $info['quicktime']['video']['frame_count'] = $framecount;
     270                                }
     271                                break;
     272
     273
     274                        case 'aART': // Album ARTist
     275                        case 'catg': // CaTeGory
     276                        case 'covr': // COVeR artwork
     277                        case 'cpil': // ComPILation
     278                        case 'cprt': // CoPyRighT
     279                        case 'desc': // DESCription
     280                        case 'disk': // DISK number
     281                        case 'egid': // Episode Global ID
     282                        case 'gnre': // GeNRE
     283                        case 'keyw': // KEYWord
     284                        case 'ldes':
     285                        case 'pcst': // PodCaST
     286                        case 'pgap': // GAPless Playback
     287                        case 'purd': // PURchase Date
     288                        case 'purl': // Podcast URL
     289                        case 'rati':
     290                        case 'rndu':
     291                        case 'rpdu':
     292                        case 'rtng': // RaTiNG
     293                        case 'stik':
     294                        case 'tmpo': // TeMPO (BPM)
     295                        case 'trkn': // TRacK Number
     296                        case 'tves': // TV EpiSode
     297                        case 'tvnn': // TV Network Name
     298                        case 'tvsh': // TV SHow Name
     299                        case 'tvsn': // TV SeasoN
     300                        case 'akID': // iTunes store account type
     301                        case 'apID':
     302                        case 'atID':
     303                        case 'cmID':
     304                        case 'cnID':
     305                        case 'geID':
     306                        case 'plID':
     307                        case 'sfID': // iTunes store country
     308                        case '©alb': // ALBum
     309                        case '©art': // ARTist
     310                        case '©ART':
     311                        case '©aut':
     312                        case '©cmt': // CoMmenT
     313                        case '©com': // COMposer
     314                        case '©cpy':
     315                        case '©day': // content created year
     316                        case '©dir':
     317                        case '©ed1':
     318                        case '©ed2':
     319                        case '©ed3':
     320                        case '©ed4':
     321                        case '©ed5':
     322                        case '©ed6':
     323                        case '©ed7':
     324                        case '©ed8':
     325                        case '©ed9':
     326                        case '©enc':
     327                        case '©fmt':
     328                        case '©gen': // GENre
     329                        case '©grp': // GRouPing
     330                        case '©hst':
     331                        case '©inf':
     332                        case '©lyr': // LYRics
     333                        case '©mak':
     334                        case '©mod':
     335                        case '©nam': // full NAMe
     336                        case '©ope':
     337                        case '©PRD':
     338                        case '©prd':
     339                        case '©prf':
     340                        case '©req':
     341                        case '©src':
     342                        case '©swr':
     343                        case '©too': // encoder
     344                        case '©trk': // TRacK
     345                        case '©url':
     346                        case '©wrn':
     347                        case '©wrt': // WRiTer
     348                        case '----': // itunes specific
     349                                if ($atom_parent == 'udta') {
     350                                        // User data atom handler
     351                                        $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
     352                                        $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
     353                                        $atom_structure['data']        =                           substr($atom_data, 4);
     354
     355                                        $atom_structure['language']    = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
     356                                        if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
     357                                                $info['comments']['language'][] = $atom_structure['language'];
     358                                        }
     359                                } else {
     360                                        // Apple item list box atom handler
     361                                        $atomoffset = 0;
     362                                        if (substr($atom_data, 2, 2) == "\x10\xB5") {
     363                                                // not sure what it means, but observed on iPhone4 data.
     364                                                // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data
     365                                                while ($atomoffset < strlen($atom_data)) {
     366                                                        $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset,     2));
     367                                                        $boxsmalltype =                           substr($atom_data, $atomoffset + 2, 2);
     368                                                        $boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
     369                                                        switch ($boxsmalltype) {
     370                                                                case "\x10\xB5":
     371                                                                        $atom_structure['data'] = $boxsmalldata;
     372                                                                        break;
     373                                                                default:
     374                                                                        $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset;
     375                                                                        $atom_structure['data'] = $atom_data;
     376                                                                        break;
     377                                                        }
     378                                                        $atomoffset += (4 + $boxsmallsize);
     379                                                }
     380                                        } else {
     381                                                while ($atomoffset < strlen($atom_data)) {
     382                                                        $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
     383                                                        $boxtype =                           substr($atom_data, $atomoffset + 4, 4);
     384                                                        $boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
     385                                                        if ($boxsize <= 1) {
     386                                                                $info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.$atomname.'" at offset: '.($atom_structure['offset'] + $atomoffset);
     387                                                                $atom_structure['data'] = null;
     388                                                                $atomoffset = strlen($atom_data);
     389                                                                break;
     390                                                        }
     391                                                        $atomoffset += $boxsize;
     392
     393                                                        switch ($boxtype) {
     394                                                                case 'mean':
     395                                                                case 'name':
     396                                                                        $atom_structure[$boxtype] = substr($boxdata, 4);
     397                                                                        break;
     398
     399                                                                case 'data':
     400                                                                        $atom_structure['version']   = getid3_lib::BigEndian2Int(substr($boxdata,  0, 1));
     401                                                                        $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata,  1, 3));
     402                                                                        switch ($atom_structure['flags_raw']) {
     403                                                                                case 0:  // data flag
     404                                                                                case 21: // tmpo/cpil flag
     405                                                                                        switch ($atomname) {
     406                                                                                                case 'cpil':
     407                                                                                                case 'pcst':
     408                                                                                                case 'pgap':
     409                                                                                                        $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
     410                                                                                                        break;
     411
     412                                                                                                case 'tmpo':
     413                                                                                                        $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
     414                                                                                                        break;
     415
     416                                                                                                case 'disk':
     417                                                                                                case 'trkn':
     418                                                                                                        $num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
     419                                                                                                        $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
     420                                                                                                        $atom_structure['data']  = empty($num) ? '' : $num;
     421                                                                                                        $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
     422                                                                                                        break;
     423
     424                                                                                                case 'gnre':
     425                                                                                                        $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
     426                                                                                                        $atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
     427                                                                                                        break;
     428
     429                                                                                                case 'rtng':
     430                                                                                                        $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
     431                                                                                                        $atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
     432                                                                                                        break;
     433
     434                                                                                                case 'stik':
     435                                                                                                        $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
     436                                                                                                        $atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
     437                                                                                                        break;
     438
     439                                                                                                case 'sfID':
     440                                                                                                        $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
     441                                                                                                        $atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
     442                                                                                                        break;
     443
     444                                                                                                case 'egid':
     445                                                                                                case 'purl':
     446                                                                                                        $atom_structure['data'] = substr($boxdata, 8);
     447                                                                                                        break;
     448
     449                                                                                                default:
     450                                                                                                        $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
     451                                                                                        }
     452                                                                                        break;
     453
     454                                                                                case 1:  // text flag
     455                                                                                case 13: // image flag
     456                                                                                default:
     457                                                                                        $atom_structure['data'] = substr($boxdata, 8);
     458                                                                                        break;
     459
     460                                                                        }
     461                                                                        break;
     462
     463                                                                default:
     464                                                                        $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset;
     465                                                                        $atom_structure['data'] = $atom_data;
     466
     467                                                        }
     468                                                }
     469                                        }
     470                                }
     471                                $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']);
     472                                break;
     473
     474
     475                        case 'play': // auto-PLAY atom
     476                                $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     477
     478                                $info['quicktime']['autoplay'] = $atom_structure['autoplay'];
     479                                break;
     480
     481
     482                        case 'WLOC': // Window LOCation atom
     483                                $atom_structure['location_x']  = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2));
     484                                $atom_structure['location_y']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 2));
     485                                break;
     486
     487
     488                        case 'LOOP': // LOOPing atom
     489                        case 'SelO': // play SELection Only atom
     490                        case 'AllF': // play ALL Frames atom
     491                                $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data);
     492                                break;
     493
     494
     495                        case 'name': //
     496                        case 'MCPS': // Media Cleaner PRo
     497                        case '@PRM': // adobe PReMiere version
     498                        case '@PRQ': // adobe PRemiere Quicktime version
     499                                $atom_structure['data'] = $atom_data;
     500                                break;
     501
     502
     503                        case 'cmvd': // Compressed MooV Data atom
     504                                // Code by ubergeekØubergeek*tv based on information from
     505                                // http://developer.apple.com/quicktime/icefloe/dispatch012.html
     506                                $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
     507
     508                                $CompressedFileData = substr($atom_data, 4);
     509                                if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
     510                                        $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
     511                                } else {
     512                                        $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];
     513                                }
     514                                break;
     515
     516
     517                        case 'dcom': // Data COMpression atom
     518                                $atom_structure['compression_id']   = $atom_data;
     519                                $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data);
     520                                break;
     521
     522
     523                        case 'rdrf': // Reference movie Data ReFerence atom
     524                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     525                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
     526                                $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001);
     527
     528                                $atom_structure['reference_type_name']    =                           substr($atom_data,  4, 4);
     529                                $atom_structure['reference_length']       = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     530                                switch ($atom_structure['reference_type_name']) {
     531                                        case 'url ':
     532                                                $atom_structure['url']            =       $this->NoNullString(substr($atom_data, 12));
     533                                                break;
     534
     535                                        case 'alis':
     536                                                $atom_structure['file_alias']     =                           substr($atom_data, 12);
     537                                                break;
     538
     539                                        case 'rsrc':
     540                                                $atom_structure['resource_alias'] =                           substr($atom_data, 12);
     541                                                break;
     542
     543                                        default:
     544                                                $atom_structure['data']           =                           substr($atom_data, 12);
     545                                                break;
     546                                }
     547                                break;
     548
     549
     550                        case 'rmqu': // Reference Movie QUality atom
     551                                $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data);
     552                                break;
     553
     554
     555                        case 'rmcs': // Reference Movie Cpu Speed atom
     556                                $atom_structure['version']          = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     557                                $atom_structure['flags_raw']        = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     558                                $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     559                                break;
     560
     561
     562                        case 'rmvc': // Reference Movie Version Check atom
     563                                $atom_structure['version']            = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     564                                $atom_structure['flags_raw']          = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     565                                $atom_structure['gestalt_selector']   =                           substr($atom_data,  4, 4);
     566                                $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     567                                $atom_structure['gestalt_value']      = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
     568                                $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
     569                                break;
     570
     571
     572                        case 'rmcd': // Reference Movie Component check atom
     573                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     574                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     575                                $atom_structure['component_type']         =                           substr($atom_data,  4, 4);
     576                                $atom_structure['component_subtype']      =                           substr($atom_data,  8, 4);
     577                                $atom_structure['component_manufacturer'] =                           substr($atom_data, 12, 4);
     578                                $atom_structure['component_flags_raw']    = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
     579                                $atom_structure['component_flags_mask']   = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
     580                                $atom_structure['component_min_version']  = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4));
     581                                break;
     582
     583
     584                        case 'rmdr': // Reference Movie Data Rate atom
     585                                $atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     586                                $atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     587                                $atom_structure['data_rate']     = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     588
     589                                $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10;
     590                                break;
     591
     592
     593                        case 'rmla': // Reference Movie Language Atom
     594                                $atom_structure['version']     = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     595                                $atom_structure['flags_raw']   = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     596                                $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     597
     598                                $atom_structure['language']    = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
     599                                if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
     600                                        $info['comments']['language'][] = $atom_structure['language'];
     601                                }
     602                                break;
     603
     604
     605                        case 'rmla': // Reference Movie Language Atom
     606                                $atom_structure['version']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     607                                $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     608                                $atom_structure['track_id']  = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     609                                break;
     610
     611
     612                        case 'ptv ': // Print To Video - defines a movie's full screen mode
     613                                // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm
     614                                $atom_structure['display_size_raw']  = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
     615                                $atom_structure['reserved_1']        = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000
     616                                $atom_structure['reserved_2']        = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000
     617                                $atom_structure['slide_show_flag']   = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1));
     618                                $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1));
     619
     620                                $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
     621                                $atom_structure['flags']['slide_show']   = (bool) $atom_structure['slide_show_flag'];
     622
     623                                $ptv_lookup[0] = 'normal';
     624                                $ptv_lookup[1] = 'double';
     625                                $ptv_lookup[2] = 'half';
     626                                $ptv_lookup[3] = 'full';
     627                                $ptv_lookup[4] = 'current';
     628                                if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
     629                                        $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
     630                                } else {
     631                                        $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')';
     632                                }
     633                                break;
     634
     635
     636                        case 'stsd': // Sample Table Sample Description atom
     637                                $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     638                                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     639                                $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     640                                $stsdEntriesDataOffset = 8;
     641                                for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     642                                        $atom_structure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
     643                                        $stsdEntriesDataOffset += 4;
     644                                        $atom_structure['sample_description_table'][$i]['data_format']      =                           substr($atom_data, $stsdEntriesDataOffset, 4);
     645                                        $stsdEntriesDataOffset += 4;
     646                                        $atom_structure['sample_description_table'][$i]['reserved']         = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6));
     647                                        $stsdEntriesDataOffset += 6;
     648                                        $atom_structure['sample_description_table'][$i]['reference_index']  = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2));
     649                                        $stsdEntriesDataOffset += 2;
     650                                        $atom_structure['sample_description_table'][$i]['data']             =                           substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
     651                                        $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
     652
     653                                        $atom_structure['sample_description_table'][$i]['encoder_version']  = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  0, 2));
     654                                        $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  2, 2));
     655                                        $atom_structure['sample_description_table'][$i]['encoder_vendor']   =                           substr($atom_structure['sample_description_table'][$i]['data'],  4, 4);
     656
     657                                        switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) {
     658
     659                                                case "\x00\x00\x00\x00":
     660                                                        // audio atom
     661                                                        $atom_structure['sample_description_table'][$i]['audio_channels']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  8,  2));
     662                                                        $atom_structure['sample_description_table'][$i]['audio_bit_depth']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10,  2));
     663                                                        $atom_structure['sample_description_table'][$i]['audio_compression_id'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12,  2));
     664                                                        $atom_structure['sample_description_table'][$i]['audio_packet_size']    =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14,  2));
     665                                                        $atom_structure['sample_description_table'][$i]['audio_sample_rate']    = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16,  4));
     666
     667                                                        switch ($atom_structure['sample_description_table'][$i]['data_format']) {
     668                                                                case 'avc1':
     669                                                                case 'mp4v':
     670                                                                        $info['fileformat'] = 'mp4';
     671                                                                        $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
     672                                                                        //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported?
     673                                                                        break;
     674
     675                                                                case 'qtvr':
     676                                                                        $info['video']['dataformat'] = 'quicktimevr';
     677                                                                        break;
     678
     679                                                                case 'mp4a':
     680                                                                default:
     681                                                                        $info['quicktime']['audio']['codec']       = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
     682                                                                        $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
     683                                                                        $info['quicktime']['audio']['channels']    = $atom_structure['sample_description_table'][$i]['audio_channels'];
     684                                                                        $info['quicktime']['audio']['bit_depth']   = $atom_structure['sample_description_table'][$i]['audio_bit_depth'];
     685                                                                        $info['audio']['codec']                    = $info['quicktime']['audio']['codec'];
     686                                                                        $info['audio']['sample_rate']              = $info['quicktime']['audio']['sample_rate'];
     687                                                                        $info['audio']['channels']                 = $info['quicktime']['audio']['channels'];
     688                                                                        $info['audio']['bits_per_sample']          = $info['quicktime']['audio']['bit_depth'];
     689                                                                        switch ($atom_structure['sample_description_table'][$i]['data_format']) {
     690                                                                                case 'raw ': // PCM
     691                                                                                case 'alac': // Apple Lossless Audio Codec
     692                                                                                        $info['audio']['lossless'] = true;
     693                                                                                        break;
     694                                                                                default:
     695                                                                                        $info['audio']['lossless'] = false;
     696                                                                                        break;
     697                                                                        }
     698                                                                        break;
     699                                                        }
     700                                                        break;
     701
     702                                                default:
     703                                                        switch ($atom_structure['sample_description_table'][$i]['data_format']) {
     704                                                                case 'mp4s':
     705                                                                        $info['fileformat'] = 'mp4';
     706                                                                        break;
     707
     708                                                                default:
     709                                                                        // video atom
     710                                                                        $atom_structure['sample_description_table'][$i]['video_temporal_quality']  =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  8,  4));
     711                                                                        $atom_structure['sample_description_table'][$i]['video_spatial_quality']   =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12,  4));
     712                                                                        $atom_structure['sample_description_table'][$i]['video_frame_width']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16,  2));
     713                                                                        $atom_structure['sample_description_table'][$i]['video_frame_height']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18,  2));
     714                                                                        $atom_structure['sample_description_table'][$i]['video_resolution_x']      = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20,  4));
     715                                                                        $atom_structure['sample_description_table'][$i]['video_resolution_y']      = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24,  4));
     716                                                                        $atom_structure['sample_description_table'][$i]['video_data_size']         =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28,  4));
     717                                                                        $atom_structure['sample_description_table'][$i]['video_frame_count']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32,  2));
     718                                                                        $atom_structure['sample_description_table'][$i]['video_encoder_name_len']  =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34,  1));
     719                                                                        $atom_structure['sample_description_table'][$i]['video_encoder_name']      =                             substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']);
     720                                                                        $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66,  2));
     721                                                                        $atom_structure['sample_description_table'][$i]['video_color_table_id']    =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68,  2));
     722
     723                                                                        $atom_structure['sample_description_table'][$i]['video_pixel_color_type']  = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
     724                                                                        $atom_structure['sample_description_table'][$i]['video_pixel_color_name']  = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);
     725
     726                                                                        if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
     727                                                                                $info['quicktime']['video']['codec_fourcc']        = $atom_structure['sample_description_table'][$i]['data_format'];
     728                                                                                $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
     729                                                                                $info['quicktime']['video']['codec']               = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
     730                                                                                $info['quicktime']['video']['color_depth']         = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
     731                                                                                $info['quicktime']['video']['color_depth_name']    = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];
     732
     733                                                                                $info['video']['codec']           = $info['quicktime']['video']['codec'];
     734                                                                                $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth'];
     735                                                                        }
     736                                                                        $info['video']['lossless']           = false;
     737                                                                        $info['video']['pixel_aspect_ratio'] = (float) 1;
     738                                                                        break;
     739                                                        }
     740                                                        break;
     741                                        }
     742                                        switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) {
     743                                                case 'mp4a':
     744                                                        $info['audio']['dataformat']         = 'mp4';
     745                                                        $info['quicktime']['audio']['codec'] = 'mp4';
     746                                                        break;
     747
     748                                                case '3ivx':
     749                                                case '3iv1':
     750                                                case '3iv2':
     751                                                        $info['video']['dataformat'] = '3ivx';
     752                                                        break;
     753
     754                                                case 'xvid':
     755                                                        $info['video']['dataformat'] = 'xvid';
     756                                                        break;
     757
     758                                                case 'mp4v':
     759                                                        $info['video']['dataformat'] = 'mpeg4';
     760                                                        break;
     761
     762                                                case 'divx':
     763                                                case 'div1':
     764                                                case 'div2':
     765                                                case 'div3':
     766                                                case 'div4':
     767                                                case 'div5':
     768                                                case 'div6':
     769                                                        $info['video']['dataformat'] = 'divx';
     770                                                        break;
     771
     772                                                default:
     773                                                        // do nothing
     774                                                        break;
     775                                        }
     776                                        unset($atom_structure['sample_description_table'][$i]['data']);
     777                                }
     778                                break;
     779
     780
     781                        case 'stts': // Sample Table Time-to-Sample atom
     782                                $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     783                                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     784                                $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     785                                $sttsEntriesDataOffset = 8;
     786                                //$FrameRateCalculatorArray = array();
     787                                $frames_count = 0;
     788                                for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     789                                        $atom_structure['time_to_sample_table'][$i]['sample_count']    = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
     790                                        $sttsEntriesDataOffset += 4;
     791                                        $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
     792                                        $sttsEntriesDataOffset += 4;
     793
     794                                        $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count'];
     795
     796                                        // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM
     797                                        //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) {
     798                                        //      $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'];
     799                                        //      if ($stts_new_framerate <= 60) {
     800                                        //              // some atoms have durations of "1" giving a very large framerate, which probably is not right
     801                                        //              $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate);
     802                                        //      }
     803                                        //}
     804                                        //
     805                                        //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count'];
     806                                }
     807                                $info['quicktime']['stts_framecount'][] = $frames_count;
     808                                //$sttsFramesTotal  = 0;
     809                                //$sttsSecondsTotal = 0;
     810                                //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) {
     811                                //      if (($frames_per_second > 60) || ($frames_per_second < 1)) {
     812                                //              // not video FPS information, probably audio information
     813                                //              $sttsFramesTotal  = 0;
     814                                //              $sttsSecondsTotal = 0;
     815                                //              break;
     816                                //      }
     817                                //      $sttsFramesTotal  += $frame_count;
     818                                //      $sttsSecondsTotal += $frame_count / $frames_per_second;
     819                                //}
     820                                //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) {
     821                                //      if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) {
     822                                //              $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal;
     823                                //      }
     824                                //}
     825                                break;
     826
     827
     828                        case 'stss': // Sample Table Sync Sample (key frames) atom
     829                                if ($ParseAllPossibleAtoms) {
     830                                        $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     831                                        $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     832                                        $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     833                                        $stssEntriesDataOffset = 8;
     834                                        for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     835                                                $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4));
     836                                                $stssEntriesDataOffset += 4;
     837                                        }
     838                                }
     839                                break;
     840
     841
     842                        case 'stsc': // Sample Table Sample-to-Chunk atom
     843                                if ($ParseAllPossibleAtoms) {
     844                                        $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     845                                        $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     846                                        $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     847                                        $stscEntriesDataOffset = 8;
     848                                        for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     849                                                $atom_structure['sample_to_chunk_table'][$i]['first_chunk']        = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
     850                                                $stscEntriesDataOffset += 4;
     851                                                $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk']  = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
     852                                                $stscEntriesDataOffset += 4;
     853                                                $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
     854                                                $stscEntriesDataOffset += 4;
     855                                        }
     856                                }
     857                                break;
     858
     859
     860                        case 'stsz': // Sample Table SiZe atom
     861                                if ($ParseAllPossibleAtoms) {
     862                                        $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     863                                        $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     864                                        $atom_structure['sample_size']    = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     865                                        $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     866                                        $stszEntriesDataOffset = 12;
     867                                        if ($atom_structure['sample_size'] == 0) {
     868                                                for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     869                                                        $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4));
     870                                                        $stszEntriesDataOffset += 4;
     871                                                }
     872                                        }
     873                                }
     874                                break;
     875
     876
     877                        case 'stco': // Sample Table Chunk Offset atom
     878                                if ($ParseAllPossibleAtoms) {
     879                                        $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     880                                        $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     881                                        $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     882                                        $stcoEntriesDataOffset = 8;
     883                                        for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     884                                                $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4));
     885                                                $stcoEntriesDataOffset += 4;
     886                                        }
     887                                }
     888                                break;
     889
     890
     891                        case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files)
     892                                if ($ParseAllPossibleAtoms) {
     893                                        $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     894                                        $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     895                                        $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     896                                        $stcoEntriesDataOffset = 8;
     897                                        for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     898                                                $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8));
     899                                                $stcoEntriesDataOffset += 8;
     900                                        }
     901                                }
     902                                break;
     903
     904
     905                        case 'dref': // Data REFerence atom
     906                                $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     907                                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     908                                $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     909                                $drefDataOffset = 8;
     910                                for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     911                                        $atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
     912                                        $drefDataOffset += 4;
     913                                        $atom_structure['data_references'][$i]['type']                    =               substr($atom_data, $drefDataOffset, 4);
     914                                        $drefDataOffset += 4;
     915                                        $atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
     916                                        $drefDataOffset += 1;
     917                                        $atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
     918                                        $drefDataOffset += 3;
     919                                        $atom_structure['data_references'][$i]['data']                    =               substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
     920                                        $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
     921
     922                                        $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
     923                                }
     924                                break;
     925
     926
     927                        case 'gmin': // base Media INformation atom
     928                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     929                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     930                                $atom_structure['graphics_mode']          = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     931                                $atom_structure['opcolor_red']            = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
     932                                $atom_structure['opcolor_green']          = getid3_lib::BigEndian2Int(substr($atom_data,  8, 2));
     933                                $atom_structure['opcolor_blue']           = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
     934                                $atom_structure['balance']                = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2));
     935                                $atom_structure['reserved']               = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
     936                                break;
     937
     938
     939                        case 'smhd': // Sound Media information HeaDer atom
     940                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     941                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     942                                $atom_structure['balance']                = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     943                                $atom_structure['reserved']               = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
     944                                break;
     945
     946
     947                        case 'vmhd': // Video Media information HeaDer atom
     948                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     949                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
     950                                $atom_structure['graphics_mode']          = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
     951                                $atom_structure['opcolor_red']            = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
     952                                $atom_structure['opcolor_green']          = getid3_lib::BigEndian2Int(substr($atom_data,  8, 2));
     953                                $atom_structure['opcolor_blue']           = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
     954
     955                                $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001);
     956                                break;
     957
     958
     959                        case 'hdlr': // HanDLeR reference atom
     960                                $atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     961                                $atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     962                                $atom_structure['component_type']         =                           substr($atom_data,  4, 4);
     963                                $atom_structure['component_subtype']      =                           substr($atom_data,  8, 4);
     964                                $atom_structure['component_manufacturer'] =                           substr($atom_data, 12, 4);
     965                                $atom_structure['component_flags_raw']    = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
     966                                $atom_structure['component_flags_mask']   = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
     967                                $atom_structure['component_name']         =      $this->Pascal2String(substr($atom_data, 24));
     968
     969                                if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) {
     970                                        $info['video']['dataformat'] = 'quicktimevr';
     971                                }
     972                                break;
     973
     974
     975                        case 'mdhd': // MeDia HeaDer atom
     976                                $atom_structure['version']               = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     977                                $atom_structure['flags_raw']             = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     978                                $atom_structure['creation_time']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     979                                $atom_structure['modify_time']           = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     980                                $atom_structure['time_scale']            = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
     981                                $atom_structure['duration']              = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
     982                                $atom_structure['language_id']           = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
     983                                $atom_structure['quality']               = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
     984
     985                                if ($atom_structure['time_scale'] == 0) {
     986                                        $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';
     987                                        return false;
     988                                }
     989                                $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
     990
     991                                $atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
     992                                $atom_structure['modify_time_unix']      = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
     993                                $atom_structure['playtime_seconds']      = $atom_structure['duration'] / $atom_structure['time_scale'];
     994                                $atom_structure['language']              = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
     995                                if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
     996                                        $info['comments']['language'][] = $atom_structure['language'];
     997                                }
     998                                break;
     999
     1000
     1001                        case 'pnot': // Preview atom
     1002                                $atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
     1003                                $atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
     1004                                $atom_structure['atom_type']              =               substr($atom_data,  6, 4);        // usually: 'PICT'
     1005                                $atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
     1006
     1007                                $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
     1008                                break;
     1009
     1010
     1011                        case 'crgn': // Clipping ReGioN atom
     1012                                $atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
     1013                                $atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
     1014                                $atom_structure['clipping_data'] =               substr($atom_data, 10);           // constitute a QuickDraw region.
     1015                                break;
     1016
     1017
     1018                        case 'load': // track LOAD settings atom
     1019                                $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
     1020                                $atom_structure['preload_duration']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1021                                $atom_structure['preload_flags_raw']  = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     1022                                $atom_structure['default_hints_raw']  = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
     1023
     1024                                $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020);
     1025                                $atom_structure['default_hints']['high_quality']  = (bool) ($atom_structure['default_hints_raw'] & 0x0100);
     1026                                break;
     1027
     1028
     1029                        case 'tmcd': // TiMe CoDe atom
     1030                        case 'chap': // CHAPter list atom
     1031                        case 'sync': // SYNChronization atom
     1032                        case 'scpt': // tranSCriPT atom
     1033                        case 'ssrc': // non-primary SouRCe atom
     1034                                for ($i = 0; $i < (strlen($atom_data) % 4); $i++) {
     1035                                        $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4));
     1036                                }
     1037                                break;
     1038
     1039
     1040                        case 'elst': // Edit LiST atom
     1041                                $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     1042                                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     1043                                $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1044                                for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) {
     1045                                        $atom_structure['edit_list'][$i]['track_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4));
     1046                                        $atom_structure['edit_list'][$i]['media_time']     =   getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4));
     1047                                        $atom_structure['edit_list'][$i]['media_rate']     = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4));
     1048                                }
     1049                                break;
     1050
     1051
     1052                        case 'kmat': // compressed MATte atom
     1053                                $atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     1054                                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
     1055                                $atom_structure['matte_data_raw'] =               substr($atom_data,  4);
     1056                                break;
     1057
     1058
     1059                        case 'ctab': // Color TABle atom
     1060                                $atom_structure['color_table_seed']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // hardcoded: 0x00000000
     1061                                $atom_structure['color_table_flags']  = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x8000
     1062                                $atom_structure['color_table_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2)) + 1;
     1063                                for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) {
     1064                                        $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2));
     1065                                        $atom_structure['color_table'][$colortableentry]['red']   = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2));
     1066                                        $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2));
     1067                                        $atom_structure['color_table'][$colortableentry]['blue']  = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2));
     1068                                }
     1069                                break;
     1070
     1071
     1072                        case 'mvhd': // MoVie HeaDer atom
     1073                                $atom_structure['version']            =   getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     1074                                $atom_structure['flags_raw']          =   getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
     1075                                $atom_structure['creation_time']      =   getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1076                                $atom_structure['modify_time']        =   getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     1077                                $atom_structure['time_scale']         =   getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
     1078                                $atom_structure['duration']           =   getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
     1079                                $atom_structure['preferred_rate']     = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4));
     1080                                $atom_structure['preferred_volume']   =   getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2));
     1081                                $atom_structure['reserved']           =                             substr($atom_data, 26, 10);
     1082                                $atom_structure['matrix_a']           = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4));
     1083                                $atom_structure['matrix_b']           = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
     1084                                $atom_structure['matrix_u']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4));
     1085                                $atom_structure['matrix_c']           = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
     1086                                $atom_structure['matrix_d']           = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
     1087                                $atom_structure['matrix_v']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4));
     1088                                $atom_structure['matrix_x']           = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
     1089                                $atom_structure['matrix_y']           = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
     1090                                $atom_structure['matrix_w']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
     1091                                $atom_structure['preview_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 72, 4));
     1092                                $atom_structure['preview_duration']   =   getid3_lib::BigEndian2Int(substr($atom_data, 76, 4));
     1093                                $atom_structure['poster_time']        =   getid3_lib::BigEndian2Int(substr($atom_data, 80, 4));
     1094                                $atom_structure['selection_time']     =   getid3_lib::BigEndian2Int(substr($atom_data, 84, 4));
     1095                                $atom_structure['selection_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
     1096                                $atom_structure['current_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
     1097                                $atom_structure['next_track_id']      =   getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
     1098
     1099                                if ($atom_structure['time_scale'] == 0) {
     1100                                        $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';
     1101                                        return false;
     1102                                }
     1103                                $atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
     1104                                $atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
     1105                                $info['quicktime']['time_scale']    = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
     1106                                $info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
     1107                                $info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
     1108                                break;
     1109
     1110
     1111                        case 'tkhd': // TracK HeaDer atom
     1112                                $atom_structure['version']             =   getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     1113                                $atom_structure['flags_raw']           =   getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
     1114                                $atom_structure['creation_time']       =   getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1115                                $atom_structure['modify_time']         =   getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
     1116                                $atom_structure['trackid']             =   getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
     1117                                $atom_structure['reserved1']           =   getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
     1118                                $atom_structure['duration']            =   getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
     1119                                $atom_structure['reserved2']           =   getid3_lib::BigEndian2Int(substr($atom_data, 24, 8));
     1120                                $atom_structure['layer']               =   getid3_lib::BigEndian2Int(substr($atom_data, 32, 2));
     1121                                $atom_structure['alternate_group']     =   getid3_lib::BigEndian2Int(substr($atom_data, 34, 2));
     1122                                $atom_structure['volume']              =   getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2));
     1123                                $atom_structure['reserved3']           =   getid3_lib::BigEndian2Int(substr($atom_data, 38, 2));
     1124                                $atom_structure['matrix_a']            = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
     1125                                $atom_structure['matrix_b']            = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4));
     1126                                $atom_structure['matrix_u']            = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
     1127                                $atom_structure['matrix_c']            = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
     1128                                $atom_structure['matrix_d']            = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4));
     1129                                $atom_structure['matrix_v']            = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
     1130                                $atom_structure['matrix_x']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4));
     1131                                $atom_structure['matrix_y']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
     1132                                $atom_structure['matrix_w']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4));
     1133                                $atom_structure['width']               = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4));
     1134                                $atom_structure['height']              = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4));
     1135
     1136                                $atom_structure['flags']['enabled']    = (bool) ($atom_structure['flags_raw'] & 0x0001);
     1137                                $atom_structure['flags']['in_movie']   = (bool) ($atom_structure['flags_raw'] & 0x0002);
     1138                                $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004);
     1139                                $atom_structure['flags']['in_poster']  = (bool) ($atom_structure['flags_raw'] & 0x0008);
     1140                                $atom_structure['creation_time_unix']  = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
     1141                                $atom_structure['modify_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
     1142
     1143                                if ($atom_structure['flags']['enabled'] == 1) {
     1144                                        if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) {
     1145                                                $info['video']['resolution_x'] = $atom_structure['width'];
     1146                                                $info['video']['resolution_y'] = $atom_structure['height'];
     1147                                        }
     1148                                        $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']);
     1149                                        $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']);
     1150                                        $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
     1151                                        $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
     1152                                } else {
     1153                                        // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295
     1154                                        //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); }
     1155                                        //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); }
     1156                                        //if (isset($info['quicktime']['video']))    { unset($info['quicktime']['video']);    }
     1157                                }
     1158                                break;
     1159
     1160
     1161                        case 'iods': // Initial Object DeScriptor atom
     1162                                // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h
     1163                                // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html
     1164                                $offset = 0;
     1165                                $atom_structure['version']                =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1166                                $offset += 1;
     1167                                $atom_structure['flags_raw']              =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3));
     1168                                $offset += 3;
     1169                                $atom_structure['mp4_iod_tag']            =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1170                                $offset += 1;
     1171                                $atom_structure['length']                 = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
     1172                                //$offset already adjusted by quicktime_read_mp4_descr_length()
     1173                                $atom_structure['object_descriptor_id']   =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));
     1174                                $offset += 2;
     1175                                $atom_structure['od_profile_level']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1176                                $offset += 1;
     1177                                $atom_structure['scene_profile_level']    =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1178                                $offset += 1;
     1179                                $atom_structure['audio_profile_id']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1180                                $offset += 1;
     1181                                $atom_structure['video_profile_id']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1182                                $offset += 1;
     1183                                $atom_structure['graphics_profile_level'] =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1184                                $offset += 1;
     1185
     1186                                $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields
     1187                                for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) {
     1188                                        $atom_structure['track'][$i]['ES_ID_IncTag'] =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
     1189                                        $offset += 1;
     1190                                        $atom_structure['track'][$i]['length']       = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
     1191                                        //$offset already adjusted by quicktime_read_mp4_descr_length()
     1192                                        $atom_structure['track'][$i]['track_id']     =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));
     1193                                        $offset += 4;
     1194                                }
     1195
     1196                                $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']);
     1197                                $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']);
     1198                                break;
     1199
     1200                        case 'ftyp': // FileTYPe (?) atom (for MP4 it seems)
     1201                                $atom_structure['signature'] =                           substr($atom_data,  0, 4);
     1202                                $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1203                                $atom_structure['fourcc']    =                           substr($atom_data,  8, 4);
     1204                                break;
     1205
     1206                        case 'mdat': // Media DATa atom
     1207                        case 'free': // FREE space atom
     1208                        case 'skip': // SKIP atom
     1209                        case 'wide': // 64-bit expansion placeholder atom
     1210                                // 'mdat' data is too big to deal with, contains no useful metadata
     1211                                // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
     1212
     1213                                // When writing QuickTime files, it is sometimes necessary to update an atom's size.
     1214                                // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom
     1215                                // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime
     1216                                // puts an 8-byte placeholder atom before any atoms it may have to update the size of.
     1217                                // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the
     1218                                // placeholder atom can be overwritten to obtain the necessary 8 extra bytes.
     1219                                // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ).
     1220                                break;
     1221
     1222
     1223                        case 'nsav': // NoSAVe atom
     1224                                // http://developer.apple.com/technotes/tn/tn2038.html
     1225                                $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
     1226                                break;
     1227
     1228                        case 'ctyp': // Controller TYPe atom (seen on QTVR)
     1229                                // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
     1230                                // some controller names are:
     1231                                //   0x00 + 'std' for linear movie
     1232                                //   'none' for no controls
     1233                                $atom_structure['ctyp'] = substr($atom_data, 0, 4);
     1234                                $info['quicktime']['controller'] = $atom_structure['ctyp'];
     1235                                switch ($atom_structure['ctyp']) {
     1236                                        case 'qtvr':
     1237                                                $info['video']['dataformat'] = 'quicktimevr';
     1238                                                break;
     1239                                }
     1240                                break;
     1241
     1242                        case 'pano': // PANOrama track (seen on QTVR)
     1243                                $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
     1244                                break;
     1245
     1246                        case 'hint': // HINT track
     1247                        case 'hinf': //
     1248                        case 'hinv': //
     1249                        case 'hnti': //
     1250                                $info['quicktime']['hinting'] = true;
     1251                                break;
     1252
     1253                        case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR)
     1254                                for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) {
     1255                                        $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
     1256                                }
     1257                                break;
     1258
     1259
     1260                        // Observed-but-not-handled atom types are just listed here to prevent warnings being generated
     1261                        case 'FXTC': // Something to do with Adobe After Effects (?)
     1262                        case 'PrmA':
     1263                        case 'code':
     1264                        case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html
     1265                        case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html
     1266                                                // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838]
     1267                                                // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html
     1268                                                // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html
     1269                        case 'ctts'://  STCompositionOffsetAID             - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
     1270                        case 'cslg'://  STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
     1271                        case 'sdtp'://  STSampleDependencyAID              - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
     1272                        case 'stps'://  STPartialSyncSampleAID             - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
     1273                                //$atom_structure['data'] = $atom_data;
     1274                                break;
     1275
     1276                        case '©xyz':  // GPS latitude+longitude+altitude
     1277                                $atom_structure['data'] = $atom_data;
     1278                                if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) {
     1279                                        @list($all, $latitude, $longitude, $altitude) = $matches;
     1280                                        $info['quicktime']['comments']['gps_latitude'][]  = floatval($latitude);
     1281                                        $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
     1282                                        if (!empty($altitude)) {
     1283                                                $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
     1284                                        }
     1285                                } else {
     1286                                        $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';
     1287                                }
     1288                                break;
     1289
     1290                        case 'NCDT':
     1291                                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
     1292                                // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
     1293                                $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
     1294                                break;
     1295                        case 'NCTH': // Nikon Camera THumbnail image
     1296                        case 'NCVW': // Nikon Camera preVieW image
     1297                                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
     1298                                if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
     1299                                        $atom_structure['data'] = $atom_data;
     1300                                        $atom_structure['image_mime'] = 'image/jpeg';
     1301                                        $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image'));
     1302                                        $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']);
     1303                                }
     1304                                break;
     1305                        case 'NCHD': // MakerNoteVersion
     1306                                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
     1307                                $atom_structure['data'] = $atom_data;
     1308                                break;
     1309                        case 'NCTG': // NikonTags
     1310                                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
     1311                                $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data);
     1312                                break;
     1313                        case 'NCDB': // NikonTags
     1314                                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
     1315                                $atom_structure['data'] = $atom_data;
     1316                                break;
     1317
     1318                        case "\x00\x00\x00\x00":
     1319                        case 'meta': // METAdata atom
     1320                                // some kind of metacontainer, may contain a big data dump such as:
     1321                                // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst
    01322 data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/
    11323 data DE4.3.1 data DEiPhone 4
     1324                                // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
     1325
     1326                    $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
     1327                    $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
     1328                    $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     1329                                //$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     1330                                break;
     1331
     1332                        case 'data': // metaDATA atom
     1333                                // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
     1334                                $atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
     1335                                $atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
     1336                                $atom_structure['data']     =                           substr($atom_data, 4 + 4);
     1337                                break;
     1338
     1339                        default:
     1340                                $info['warning'][] = 'Unknown QuickTime atom type: "'.$atomname.'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset;
     1341                                $atom_structure['data'] = $atom_data;
     1342                                break;
     1343                }
     1344                array_pop($atomHierarchy);
     1345                return $atom_structure;
     1346        }
     1347
     1348        public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
     1349//echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'<br><br>';
     1350                $atom_structure  = false;
     1351                $subatomoffset  = 0;
     1352                $subatomcounter = 0;
     1353                if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
     1354                        return false;
     1355                }
     1356                while ($subatomoffset < strlen($atom_data)) {
     1357                        $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4));
     1358                        $subatomname =                           substr($atom_data, $subatomoffset + 4, 4);
     1359                        $subatomdata =                           substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
     1360                        if ($subatomsize == 0) {
     1361                                // Furthermore, for historical reasons the list of atoms is optionally
     1362                                // terminated by a 32-bit integer set to 0. If you are writing a program
     1363                                // to read user data atoms, you should allow for the terminating 0.
     1364                                return $atom_structure;
     1365                        }
     1366
     1367                        $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
     1368
     1369                        $subatomoffset += $subatomsize;
     1370                        $subatomcounter++;
     1371                }
     1372                return $atom_structure;
     1373        }
     1374
     1375
     1376        public function quicktime_read_mp4_descr_length($data, &$offset) {
     1377                // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html
     1378                $num_bytes = 0;
     1379                $length    = 0;
     1380                do {
     1381                        $b = ord(substr($data, $offset++, 1));
     1382                        $length = ($length << 7) | ($b & 0x7F);
     1383                } while (($b & 0x80) && ($num_bytes++ < 4));
     1384                return $length;
     1385        }
     1386
     1387
     1388        public function QuicktimeLanguageLookup($languageid) {
     1389                static $QuicktimeLanguageLookup = array();
     1390                if (empty($QuicktimeLanguageLookup)) {
     1391                        $QuicktimeLanguageLookup[0]   = 'English';
     1392                        $QuicktimeLanguageLookup[1]   = 'French';
     1393                        $QuicktimeLanguageLookup[2]   = 'German';
     1394                        $QuicktimeLanguageLookup[3]   = 'Italian';
     1395                        $QuicktimeLanguageLookup[4]   = 'Dutch';
     1396                        $QuicktimeLanguageLookup[5]   = 'Swedish';
     1397                        $QuicktimeLanguageLookup[6]   = 'Spanish';
     1398                        $QuicktimeLanguageLookup[7]   = 'Danish';
     1399                        $QuicktimeLanguageLookup[8]   = 'Portuguese';
     1400                        $QuicktimeLanguageLookup[9]   = 'Norwegian';
     1401                        $QuicktimeLanguageLookup[10]  = 'Hebrew';
     1402                        $QuicktimeLanguageLookup[11]  = 'Japanese';
     1403                        $QuicktimeLanguageLookup[12]  = 'Arabic';
     1404                        $QuicktimeLanguageLookup[13]  = 'Finnish';
     1405                        $QuicktimeLanguageLookup[14]  = 'Greek';
     1406                        $QuicktimeLanguageLookup[15]  = 'Icelandic';
     1407                        $QuicktimeLanguageLookup[16]  = 'Maltese';
     1408                        $QuicktimeLanguageLookup[17]  = 'Turkish';
     1409                        $QuicktimeLanguageLookup[18]  = 'Croatian';
     1410                        $QuicktimeLanguageLookup[19]  = 'Chinese (Traditional)';
     1411                        $QuicktimeLanguageLookup[20]  = 'Urdu';
     1412                        $QuicktimeLanguageLookup[21]  = 'Hindi';
     1413                        $QuicktimeLanguageLookup[22]  = 'Thai';
     1414                        $QuicktimeLanguageLookup[23]  = 'Korean';
     1415                        $QuicktimeLanguageLookup[24]  = 'Lithuanian';
     1416                        $QuicktimeLanguageLookup[25]  = 'Polish';
     1417                        $QuicktimeLanguageLookup[26]  = 'Hungarian';
     1418                        $QuicktimeLanguageLookup[27]  = 'Estonian';
     1419                        $QuicktimeLanguageLookup[28]  = 'Lettish';
     1420                        $QuicktimeLanguageLookup[28]  = 'Latvian';
     1421                        $QuicktimeLanguageLookup[29]  = 'Saamisk';
     1422                        $QuicktimeLanguageLookup[29]  = 'Lappish';
     1423                        $QuicktimeLanguageLookup[30]  = 'Faeroese';
     1424                        $QuicktimeLanguageLookup[31]  = 'Farsi';
     1425                        $QuicktimeLanguageLookup[31]  = 'Persian';
     1426                        $QuicktimeLanguageLookup[32]  = 'Russian';
     1427                        $QuicktimeLanguageLookup[33]  = 'Chinese (Simplified)';
     1428                        $QuicktimeLanguageLookup[34]  = 'Flemish';
     1429                        $QuicktimeLanguageLookup[35]  = 'Irish';
     1430                        $QuicktimeLanguageLookup[36]  = 'Albanian';
     1431                        $QuicktimeLanguageLookup[37]  = 'Romanian';
     1432                        $QuicktimeLanguageLookup[38]  = 'Czech';
     1433                        $QuicktimeLanguageLookup[39]  = 'Slovak';
     1434                        $QuicktimeLanguageLookup[40]  = 'Slovenian';
     1435                        $QuicktimeLanguageLookup[41]  = 'Yiddish';
     1436                        $QuicktimeLanguageLookup[42]  = 'Serbian';
     1437                        $QuicktimeLanguageLookup[43]  = 'Macedonian';
     1438                        $QuicktimeLanguageLookup[44]  = 'Bulgarian';
     1439                        $QuicktimeLanguageLookup[45]  = 'Ukrainian';
     1440                        $QuicktimeLanguageLookup[46]  = 'Byelorussian';
     1441                        $QuicktimeLanguageLookup[47]  = 'Uzbek';
     1442                        $QuicktimeLanguageLookup[48]  = 'Kazakh';
     1443                        $QuicktimeLanguageLookup[49]  = 'Azerbaijani';
     1444                        $QuicktimeLanguageLookup[50]  = 'AzerbaijanAr';
     1445                        $QuicktimeLanguageLookup[51]  = 'Armenian';
     1446                        $QuicktimeLanguageLookup[52]  = 'Georgian';
     1447                        $QuicktimeLanguageLookup[53]  = 'Moldavian';
     1448                        $QuicktimeLanguageLookup[54]  = 'Kirghiz';
     1449                        $QuicktimeLanguageLookup[55]  = 'Tajiki';
     1450                        $QuicktimeLanguageLookup[56]  = 'Turkmen';
     1451                        $QuicktimeLanguageLookup[57]  = 'Mongolian';
     1452                        $QuicktimeLanguageLookup[58]  = 'MongolianCyr';
     1453                        $QuicktimeLanguageLookup[59]  = 'Pashto';
     1454                        $QuicktimeLanguageLookup[60]  = 'Kurdish';
     1455                        $QuicktimeLanguageLookup[61]  = 'Kashmiri';
     1456                        $QuicktimeLanguageLookup[62]  = 'Sindhi';
     1457                        $QuicktimeLanguageLookup[63]  = 'Tibetan';
     1458                        $QuicktimeLanguageLookup[64]  = 'Nepali';
     1459                        $QuicktimeLanguageLookup[65]  = 'Sanskrit';
     1460                        $QuicktimeLanguageLookup[66]  = 'Marathi';
     1461                        $QuicktimeLanguageLookup[67]  = 'Bengali';
     1462                        $QuicktimeLanguageLookup[68]  = 'Assamese';
     1463                        $QuicktimeLanguageLookup[69]  = 'Gujarati';
     1464                        $QuicktimeLanguageLookup[70]  = 'Punjabi';
     1465                        $QuicktimeLanguageLookup[71]  = 'Oriya';
     1466                        $QuicktimeLanguageLookup[72]  = 'Malayalam';
     1467                        $QuicktimeLanguageLookup[73]  = 'Kannada';
     1468                        $QuicktimeLanguageLookup[74]  = 'Tamil';
     1469                        $QuicktimeLanguageLookup[75]  = 'Telugu';
     1470                        $QuicktimeLanguageLookup[76]  = 'Sinhalese';
     1471                        $QuicktimeLanguageLookup[77]  = 'Burmese';
     1472                        $QuicktimeLanguageLookup[78]  = 'Khmer';
     1473                        $QuicktimeLanguageLookup[79]  = 'Lao';
     1474                        $QuicktimeLanguageLookup[80]  = 'Vietnamese';
     1475                        $QuicktimeLanguageLookup[81]  = 'Indonesian';
     1476                        $QuicktimeLanguageLookup[82]  = 'Tagalog';
     1477                        $QuicktimeLanguageLookup[83]  = 'MalayRoman';
     1478                        $QuicktimeLanguageLookup[84]  = 'MalayArabic';
     1479                        $QuicktimeLanguageLookup[85]  = 'Amharic';
     1480                        $QuicktimeLanguageLookup[86]  = 'Tigrinya';
     1481                        $QuicktimeLanguageLookup[87]  = 'Galla';
     1482                        $QuicktimeLanguageLookup[87]  = 'Oromo';
     1483                        $QuicktimeLanguageLookup[88]  = 'Somali';
     1484                        $QuicktimeLanguageLookup[89]  = 'Swahili';
     1485                        $QuicktimeLanguageLookup[90]  = 'Ruanda';
     1486                        $QuicktimeLanguageLookup[91]  = 'Rundi';
     1487                        $QuicktimeLanguageLookup[92]  = 'Chewa';
     1488                        $QuicktimeLanguageLookup[93]  = 'Malagasy';
     1489                        $QuicktimeLanguageLookup[94]  = 'Esperanto';
     1490                        $QuicktimeLanguageLookup[128] = 'Welsh';
     1491                        $QuicktimeLanguageLookup[129] = 'Basque';
     1492                        $QuicktimeLanguageLookup[130] = 'Catalan';
     1493                        $QuicktimeLanguageLookup[131] = 'Latin';
     1494                        $QuicktimeLanguageLookup[132] = 'Quechua';
     1495                        $QuicktimeLanguageLookup[133] = 'Guarani';
     1496                        $QuicktimeLanguageLookup[134] = 'Aymara';
     1497                        $QuicktimeLanguageLookup[135] = 'Tatar';
     1498                        $QuicktimeLanguageLookup[136] = 'Uighur';
     1499                        $QuicktimeLanguageLookup[137] = 'Dzongkha';
     1500                        $QuicktimeLanguageLookup[138] = 'JavaneseRom';
     1501                }
     1502                return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid');
     1503        }
     1504
     1505        public function QuicktimeVideoCodecLookup($codecid) {
     1506                static $QuicktimeVideoCodecLookup = array();
     1507                if (empty($QuicktimeVideoCodecLookup)) {
     1508                        $QuicktimeVideoCodecLookup['.SGI'] = 'SGI';
     1509                        $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1';
     1510                        $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2';
     1511                        $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4';
     1512                        $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB';
     1513                        $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC';
     1514                        $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG';
     1515                        $QuicktimeVideoCodecLookup['b16g'] = '16Gray';
     1516                        $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray';
     1517                        $QuicktimeVideoCodecLookup['b48r'] = '48RGB';
     1518                        $QuicktimeVideoCodecLookup['b64a'] = '64ARGB';
     1519                        $QuicktimeVideoCodecLookup['base'] = 'Base';
     1520                        $QuicktimeVideoCodecLookup['clou'] = 'Cloud';
     1521                        $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK';
     1522                        $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak';
     1523                        $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG';
     1524                        $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC';
     1525                        $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL';
     1526                        $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC';
     1527                        $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL';
     1528                        $QuicktimeVideoCodecLookup['fire'] = 'Fire';
     1529                        $QuicktimeVideoCodecLookup['flic'] = 'FLC';
     1530                        $QuicktimeVideoCodecLookup['gif '] = 'GIF';
     1531                        $QuicktimeVideoCodecLookup['h261'] = 'H261';
     1532                        $QuicktimeVideoCodecLookup['h263'] = 'H263';
     1533                        $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4';
     1534                        $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG';
     1535                        $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD';
     1536                        $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A';
     1537                        $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B';
     1538                        $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1';
     1539                        $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420';
     1540                        $QuicktimeVideoCodecLookup['path'] = 'Vector';
     1541                        $QuicktimeVideoCodecLookup['png '] = 'PNG';
     1542                        $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint';
     1543                        $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX';
     1544                        $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw';
     1545                        $QuicktimeVideoCodecLookup['raw '] = 'RAW';
     1546                        $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple';
     1547                        $QuicktimeVideoCodecLookup['rpza'] = 'Video';
     1548                        $QuicktimeVideoCodecLookup['smc '] = 'Graphics';
     1549                        $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1';
     1550                        $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3';
     1551                        $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9';
     1552                        $QuicktimeVideoCodecLookup['tga '] = 'Targa';
     1553                        $QuicktimeVideoCodecLookup['tiff'] = 'TIFF';
     1554                        $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW';
     1555                        $QuicktimeVideoCodecLookup['WRLE'] = 'BMP';
     1556                        $QuicktimeVideoCodecLookup['y420'] = 'YUV420';
     1557                        $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo';
     1558                        $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned';
     1559                        $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned';
     1560                }
     1561                return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : '');
     1562        }
     1563
     1564        public function QuicktimeAudioCodecLookup($codecid) {
     1565                static $QuicktimeAudioCodecLookup = array();
     1566                if (empty($QuicktimeAudioCodecLookup)) {
     1567                        $QuicktimeAudioCodecLookup['.mp3']          = 'Fraunhofer MPEG Layer-III alias';
     1568                        $QuicktimeAudioCodecLookup['aac ']          = 'ISO/IEC 14496-3 AAC';
     1569                        $QuicktimeAudioCodecLookup['agsm']          = 'Apple GSM 10:1';
     1570                        $QuicktimeAudioCodecLookup['alac']          = 'Apple Lossless Audio Codec';
     1571                        $QuicktimeAudioCodecLookup['alaw']          = 'A-law 2:1';
     1572                        $QuicktimeAudioCodecLookup['conv']          = 'Sample Format';
     1573                        $QuicktimeAudioCodecLookup['dvca']          = 'DV';
     1574                        $QuicktimeAudioCodecLookup['dvi ']          = 'DV 4:1';
     1575                        $QuicktimeAudioCodecLookup['eqal']          = 'Frequency Equalizer';
     1576                        $QuicktimeAudioCodecLookup['fl32']          = '32-bit Floating Point';
     1577                        $QuicktimeAudioCodecLookup['fl64']          = '64-bit Floating Point';
     1578                        $QuicktimeAudioCodecLookup['ima4']          = 'Interactive Multimedia Association 4:1';
     1579                        $QuicktimeAudioCodecLookup['in24']          = '24-bit Integer';
     1580                        $QuicktimeAudioCodecLookup['in32']          = '32-bit Integer';
     1581                        $QuicktimeAudioCodecLookup['lpc ']          = 'LPC 23:1';
     1582                        $QuicktimeAudioCodecLookup['MAC3']          = 'Macintosh Audio Compression/Expansion (MACE) 3:1';
     1583                        $QuicktimeAudioCodecLookup['MAC6']          = 'Macintosh Audio Compression/Expansion (MACE) 6:1';
     1584                        $QuicktimeAudioCodecLookup['mixb']          = '8-bit Mixer';
     1585                        $QuicktimeAudioCodecLookup['mixw']          = '16-bit Mixer';
     1586                        $QuicktimeAudioCodecLookup['mp4a']          = 'ISO/IEC 14496-3 AAC';
     1587                        $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM';
     1588                        $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA';
     1589                        $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III';
     1590                        $QuicktimeAudioCodecLookup['NONE']          = 'No Encoding';
     1591                        $QuicktimeAudioCodecLookup['Qclp']          = 'Qualcomm PureVoice';
     1592                        $QuicktimeAudioCodecLookup['QDM2']          = 'QDesign Music 2';
     1593                        $QuicktimeAudioCodecLookup['QDMC']          = 'QDesign Music 1';
     1594                        $QuicktimeAudioCodecLookup['ratb']          = '8-bit Rate';
     1595                        $QuicktimeAudioCodecLookup['ratw']          = '16-bit Rate';
     1596                        $QuicktimeAudioCodecLookup['raw ']          = 'raw PCM';
     1597                        $QuicktimeAudioCodecLookup['sour']          = 'Sound Source';
     1598                        $QuicktimeAudioCodecLookup['sowt']          = 'signed/two\'s complement (Little Endian)';
     1599                        $QuicktimeAudioCodecLookup['str1']          = 'Iomega MPEG layer II';
     1600                        $QuicktimeAudioCodecLookup['str2']          = 'Iomega MPEG *layer II';
     1601                        $QuicktimeAudioCodecLookup['str3']          = 'Iomega MPEG **layer II';
     1602                        $QuicktimeAudioCodecLookup['str4']          = 'Iomega MPEG ***layer II';
     1603                        $QuicktimeAudioCodecLookup['twos']          = 'signed/two\'s complement (Big Endian)';
     1604                        $QuicktimeAudioCodecLookup['ulaw']          = 'mu-law 2:1';
     1605                }
     1606                return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : '');
     1607        }
     1608
     1609        public function QuicktimeDCOMLookup($compressionid) {
     1610                static $QuicktimeDCOMLookup = array();
     1611                if (empty($QuicktimeDCOMLookup)) {
     1612                        $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate';
     1613                        $QuicktimeDCOMLookup['adec'] = 'Apple Compression';
     1614                }
     1615                return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : '');
     1616        }
     1617
     1618        public function QuicktimeColorNameLookup($colordepthid) {
     1619                static $QuicktimeColorNameLookup = array();
     1620                if (empty($QuicktimeColorNameLookup)) {
     1621                        $QuicktimeColorNameLookup[1]  = '2-color (monochrome)';
     1622                        $QuicktimeColorNameLookup[2]  = '4-color';
     1623                        $QuicktimeColorNameLookup[4]  = '16-color';
     1624                        $QuicktimeColorNameLookup[8]  = '256-color';
     1625                        $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)';
     1626                        $QuicktimeColorNameLookup[24] = 'millions (24-bit color)';
     1627                        $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)';
     1628                        $QuicktimeColorNameLookup[33] = 'black & white';
     1629                        $QuicktimeColorNameLookup[34] = '4-gray';
     1630                        $QuicktimeColorNameLookup[36] = '16-gray';
     1631                        $QuicktimeColorNameLookup[40] = '256-gray';
     1632                }
     1633                return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid');
     1634        }
     1635
     1636        public function QuicktimeSTIKLookup($stik) {
     1637                static $QuicktimeSTIKLookup = array();
     1638                if (empty($QuicktimeSTIKLookup)) {
     1639                        $QuicktimeSTIKLookup[0]  = 'Movie';
     1640                        $QuicktimeSTIKLookup[1]  = 'Normal';
     1641                        $QuicktimeSTIKLookup[2]  = 'Audiobook';
     1642                        $QuicktimeSTIKLookup[5]  = 'Whacked Bookmark';
     1643                        $QuicktimeSTIKLookup[6]  = 'Music Video';
     1644                        $QuicktimeSTIKLookup[9]  = 'Short Film';
     1645                        $QuicktimeSTIKLookup[10] = 'TV Show';
     1646                        $QuicktimeSTIKLookup[11] = 'Booklet';
     1647                        $QuicktimeSTIKLookup[14] = 'Ringtone';
     1648                        $QuicktimeSTIKLookup[21] = 'Podcast';
     1649                }
     1650                return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid');
     1651        }
     1652
     1653        public function QuicktimeIODSaudioProfileName($audio_profile_id) {
     1654                static $QuicktimeIODSaudioProfileNameLookup = array();
     1655                if (empty($QuicktimeIODSaudioProfileNameLookup)) {
     1656                        $QuicktimeIODSaudioProfileNameLookup = array(
     1657                            0x00 => 'ISO Reserved (0x00)',
     1658                            0x01 => 'Main Audio Profile @ Level 1',
     1659                            0x02 => 'Main Audio Profile @ Level 2',
     1660                            0x03 => 'Main Audio Profile @ Level 3',
     1661                            0x04 => 'Main Audio Profile @ Level 4',
     1662                            0x05 => 'Scalable Audio Profile @ Level 1',
     1663                            0x06 => 'Scalable Audio Profile @ Level 2',
     1664                            0x07 => 'Scalable Audio Profile @ Level 3',
     1665                            0x08 => 'Scalable Audio Profile @ Level 4',
     1666                            0x09 => 'Speech Audio Profile @ Level 1',
     1667                            0x0A => 'Speech Audio Profile @ Level 2',
     1668                            0x0B => 'Synthetic Audio Profile @ Level 1',
     1669                            0x0C => 'Synthetic Audio Profile @ Level 2',
     1670                            0x0D => 'Synthetic Audio Profile @ Level 3',
     1671                            0x0E => 'High Quality Audio Profile @ Level 1',
     1672                            0x0F => 'High Quality Audio Profile @ Level 2',
     1673                            0x10 => 'High Quality Audio Profile @ Level 3',
     1674                            0x11 => 'High Quality Audio Profile @ Level 4',
     1675                            0x12 => 'High Quality Audio Profile @ Level 5',
     1676                            0x13 => 'High Quality Audio Profile @ Level 6',
     1677                            0x14 => 'High Quality Audio Profile @ Level 7',
     1678                            0x15 => 'High Quality Audio Profile @ Level 8',
     1679                            0x16 => 'Low Delay Audio Profile @ Level 1',
     1680                            0x17 => 'Low Delay Audio Profile @ Level 2',
     1681                            0x18 => 'Low Delay Audio Profile @ Level 3',
     1682                            0x19 => 'Low Delay Audio Profile @ Level 4',
     1683                            0x1A => 'Low Delay Audio Profile @ Level 5',
     1684                            0x1B => 'Low Delay Audio Profile @ Level 6',
     1685                            0x1C => 'Low Delay Audio Profile @ Level 7',
     1686                            0x1D => 'Low Delay Audio Profile @ Level 8',
     1687                            0x1E => 'Natural Audio Profile @ Level 1',
     1688                            0x1F => 'Natural Audio Profile @ Level 2',
     1689                            0x20 => 'Natural Audio Profile @ Level 3',
     1690                            0x21 => 'Natural Audio Profile @ Level 4',
     1691                            0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
     1692                            0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
     1693                            0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
     1694                            0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
     1695                            0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
     1696                            0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
     1697                            0x28 => 'AAC Profile @ Level 1',
     1698                            0x29 => 'AAC Profile @ Level 2',
     1699                            0x2A => 'AAC Profile @ Level 4',
     1700                            0x2B => 'AAC Profile @ Level 5',
     1701                            0x2C => 'High Efficiency AAC Profile @ Level 2',
     1702                            0x2D => 'High Efficiency AAC Profile @ Level 3',
     1703                            0x2E => 'High Efficiency AAC Profile @ Level 4',
     1704                            0x2F => 'High Efficiency AAC Profile @ Level 5',
     1705                            0xFE => 'Not part of MPEG-4 audio profiles',
     1706                            0xFF => 'No audio capability required',
     1707                        );
     1708                }
     1709                return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
     1710        }
     1711
     1712
     1713        public function QuicktimeIODSvideoProfileName($video_profile_id) {
     1714                static $QuicktimeIODSvideoProfileNameLookup = array();
     1715                if (empty($QuicktimeIODSvideoProfileNameLookup)) {
     1716                        $QuicktimeIODSvideoProfileNameLookup = array(
     1717                                0x00 => 'Reserved (0x00) Profile',
     1718                                0x01 => 'Simple Profile @ Level 1',
     1719                                0x02 => 'Simple Profile @ Level 2',
     1720                                0x03 => 'Simple Profile @ Level 3',
     1721                                0x08 => 'Simple Profile @ Level 0',
     1722                                0x10 => 'Simple Scalable Profile @ Level 0',
     1723                                0x11 => 'Simple Scalable Profile @ Level 1',
     1724                                0x12 => 'Simple Scalable Profile @ Level 2',
     1725                                0x15 => 'AVC/H264 Profile',
     1726                                0x21 => 'Core Profile @ Level 1',
     1727                                0x22 => 'Core Profile @ Level 2',
     1728                                0x32 => 'Main Profile @ Level 2',
     1729                                0x33 => 'Main Profile @ Level 3',
     1730                                0x34 => 'Main Profile @ Level 4',
     1731                                0x42 => 'N-bit Profile @ Level 2',
     1732                                0x51 => 'Scalable Texture Profile @ Level 1',
     1733                                0x61 => 'Simple Face Animation Profile @ Level 1',
     1734                                0x62 => 'Simple Face Animation Profile @ Level 2',
     1735                                0x63 => 'Simple FBA Profile @ Level 1',
     1736                                0x64 => 'Simple FBA Profile @ Level 2',
     1737                                0x71 => 'Basic Animated Texture Profile @ Level 1',
     1738                                0x72 => 'Basic Animated Texture Profile @ Level 2',
     1739                                0x81 => 'Hybrid Profile @ Level 1',
     1740                                0x82 => 'Hybrid Profile @ Level 2',
     1741                                0x91 => 'Advanced Real Time Simple Profile @ Level 1',
     1742                                0x92 => 'Advanced Real Time Simple Profile @ Level 2',
     1743                                0x93 => 'Advanced Real Time Simple Profile @ Level 3',
     1744                                0x94 => 'Advanced Real Time Simple Profile @ Level 4',
     1745                                0xA1 => 'Core Scalable Profile @ Level1',
     1746                                0xA2 => 'Core Scalable Profile @ Level2',
     1747                                0xA3 => 'Core Scalable Profile @ Level3',
     1748                                0xB1 => 'Advanced Coding Efficiency Profile @ Level 1',
     1749                                0xB2 => 'Advanced Coding Efficiency Profile @ Level 2',
     1750                                0xB3 => 'Advanced Coding Efficiency Profile @ Level 3',
     1751                                0xB4 => 'Advanced Coding Efficiency Profile @ Level 4',
     1752                                0xC1 => 'Advanced Core Profile @ Level 1',
     1753                                0xC2 => 'Advanced Core Profile @ Level 2',
     1754                                0xD1 => 'Advanced Scalable Texture @ Level1',
     1755                                0xD2 => 'Advanced Scalable Texture @ Level2',
     1756                                0xE1 => 'Simple Studio Profile @ Level 1',
     1757                                0xE2 => 'Simple Studio Profile @ Level 2',
     1758                                0xE3 => 'Simple Studio Profile @ Level 3',
     1759                                0xE4 => 'Simple Studio Profile @ Level 4',
     1760                                0xE5 => 'Core Studio Profile @ Level 1',
     1761                                0xE6 => 'Core Studio Profile @ Level 2',
     1762                                0xE7 => 'Core Studio Profile @ Level 3',
     1763                                0xE8 => 'Core Studio Profile @ Level 4',
     1764                                0xF0 => 'Advanced Simple Profile @ Level 0',
     1765                                0xF1 => 'Advanced Simple Profile @ Level 1',
     1766                                0xF2 => 'Advanced Simple Profile @ Level 2',
     1767                                0xF3 => 'Advanced Simple Profile @ Level 3',
     1768                                0xF4 => 'Advanced Simple Profile @ Level 4',
     1769                                0xF5 => 'Advanced Simple Profile @ Level 5',
     1770                                0xF7 => 'Advanced Simple Profile @ Level 3b',
     1771                                0xF8 => 'Fine Granularity Scalable Profile @ Level 0',
     1772                                0xF9 => 'Fine Granularity Scalable Profile @ Level 1',
     1773                                0xFA => 'Fine Granularity Scalable Profile @ Level 2',
     1774                                0xFB => 'Fine Granularity Scalable Profile @ Level 3',
     1775                                0xFC => 'Fine Granularity Scalable Profile @ Level 4',
     1776                                0xFD => 'Fine Granularity Scalable Profile @ Level 5',
     1777                                0xFE => 'Not part of MPEG-4 Visual profiles',
     1778                                0xFF => 'No visual capability required',
     1779                        );
     1780                }
     1781                return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile');
     1782        }
     1783
     1784
     1785        public function QuicktimeContentRatingLookup($rtng) {
     1786                static $QuicktimeContentRatingLookup = array();
     1787                if (empty($QuicktimeContentRatingLookup)) {
     1788                        $QuicktimeContentRatingLookup[0]  = 'None';
     1789                        $QuicktimeContentRatingLookup[2]  = 'Clean';
     1790                        $QuicktimeContentRatingLookup[4]  = 'Explicit';
     1791                }
     1792                return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
     1793        }
     1794
     1795        public function QuicktimeStoreAccountTypeLookup($akid) {
     1796                static $QuicktimeStoreAccountTypeLookup = array();
     1797                if (empty($QuicktimeStoreAccountTypeLookup)) {
     1798                        $QuicktimeStoreAccountTypeLookup[0] = 'iTunes';
     1799                        $QuicktimeStoreAccountTypeLookup[1] = 'AOL';
     1800                }
     1801                return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid');
     1802        }
     1803
     1804        public function QuicktimeStoreFrontCodeLookup($sfid) {
     1805                static $QuicktimeStoreFrontCodeLookup = array();
     1806                if (empty($QuicktimeStoreFrontCodeLookup)) {
     1807                        $QuicktimeStoreFrontCodeLookup[143460] = 'Australia';
     1808                        $QuicktimeStoreFrontCodeLookup[143445] = 'Austria';
     1809                        $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium';
     1810                        $QuicktimeStoreFrontCodeLookup[143455] = 'Canada';
     1811                        $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark';
     1812                        $QuicktimeStoreFrontCodeLookup[143447] = 'Finland';
     1813                        $QuicktimeStoreFrontCodeLookup[143442] = 'France';
     1814                        $QuicktimeStoreFrontCodeLookup[143443] = 'Germany';
     1815                        $QuicktimeStoreFrontCodeLookup[143448] = 'Greece';
     1816                        $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland';
     1817                        $QuicktimeStoreFrontCodeLookup[143450] = 'Italy';
     1818                        $QuicktimeStoreFrontCodeLookup[143462] = 'Japan';
     1819                        $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg';
     1820                        $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands';
     1821                        $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand';
     1822                        $QuicktimeStoreFrontCodeLookup[143457] = 'Norway';
     1823                        $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal';
     1824                        $QuicktimeStoreFrontCodeLookup[143454] = 'Spain';
     1825                        $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden';
     1826                        $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland';
     1827                        $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom';
     1828                        $QuicktimeStoreFrontCodeLookup[143441] = 'United States';
     1829                }
     1830                return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
     1831        }
     1832
     1833        public function QuicktimeParseNikonNCTG($atom_data) {
     1834                // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
     1835                // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
     1836                // Data is stored as records of:
     1837                // * 4 bytes record type
     1838                // * 2 bytes size of data field type:
     1839                //     0x0001 = flag   (size field *= 1-byte)
     1840                //     0x0002 = char   (size field *= 1-byte)
     1841                //     0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
     1842                //     0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
     1843                //     0x0005 = float  (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
     1844                //     0x0007 = bytes  (size field *= 1-byte), values are stored as ??????
     1845                //     0x0008 = ?????  (size field *= 2-byte), values are stored as ??????
     1846                // * 2 bytes data size field
     1847                // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
     1848                // all integers are stored BigEndian
     1849
     1850                $NCTGtagName = array(
     1851                        0x00000001 => 'Make',
     1852                        0x00000002 => 'Model',
     1853                        0x00000003 => 'Software',
     1854                        0x00000011 => 'CreateDate',
     1855                        0x00000012 => 'DateTimeOriginal',
     1856                        0x00000013 => 'FrameCount',
     1857                        0x00000016 => 'FrameRate',
     1858                        0x00000022 => 'FrameWidth',
     1859                        0x00000023 => 'FrameHeight',
     1860                        0x00000032 => 'AudioChannels',
     1861                        0x00000033 => 'AudioBitsPerSample',
     1862                        0x00000034 => 'AudioSampleRate',
     1863                        0x02000001 => 'MakerNoteVersion',
     1864                        0x02000005 => 'WhiteBalance',
     1865                        0x0200000b => 'WhiteBalanceFineTune',
     1866                        0x0200001e => 'ColorSpace',
     1867                        0x02000023 => 'PictureControlData',
     1868                        0x02000024 => 'WorldTime',
     1869                        0x02000032 => 'UnknownInfo',
     1870                        0x02000083 => 'LensType',
     1871                        0x02000084 => 'Lens',
     1872                );
     1873
     1874                $offset = 0;
     1875                $datalength = strlen($atom_data);
     1876                $parsed = array();
     1877                while ($offset < $datalength) {
     1878//echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'<br>';
     1879                        $record_type       = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));  $offset += 4;
     1880                        $data_size_type    = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));  $offset += 2;
     1881                        $data_size         = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));  $offset += 2;
     1882                        switch ($data_size_type) {
     1883                                case 0x0001: // 0x0001 = flag   (size field *= 1-byte)
     1884                                        $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
     1885                                        $offset += ($data_size * 1);
     1886                                        break;
     1887                                case 0x0002: // 0x0002 = char   (size field *= 1-byte)
     1888                                        $data = substr($atom_data, $offset, $data_size * 1);
     1889                                        $offset += ($data_size * 1);
     1890                                        $data = rtrim($data, "\x00");
     1891                                        break;
     1892                                case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
     1893                                        $data = '';
     1894                                        for ($i = $data_size - 1; $i >= 0; $i--) {
     1895                                                $data .= substr($atom_data, $offset + ($i * 2), 2);
     1896                                        }
     1897                                        $data = getid3_lib::BigEndian2Int($data);
     1898                                        $offset += ($data_size * 2);
     1899                                        break;
     1900                                case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
     1901                                        $data = '';
     1902                                        for ($i = $data_size - 1; $i >= 0; $i--) {
     1903                                                $data .= substr($atom_data, $offset + ($i * 4), 4);
     1904                                        }
     1905                                        $data = getid3_lib::BigEndian2Int($data);
     1906                                        $offset += ($data_size * 4);
     1907                                        break;
     1908                                case 0x0005: // 0x0005 = float  (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
     1909                                        $data = array();
     1910                                        for ($i = 0; $i < $data_size; $i++) {
     1911                                                $numerator    = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
     1912                                                $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
     1913                                                if ($denomninator == 0) {
     1914                                                        $data[$i] = false;
     1915                                                } else {
     1916                                                        $data[$i] = (double) $numerator / $denomninator;
     1917                                                }
     1918                                        }
     1919                                        $offset += (8 * $data_size);
     1920                                        if (count($data) == 1) {
     1921                                                $data = $data[0];
     1922                                        }
     1923                                        break;
     1924                                case 0x0007: // 0x0007 = bytes  (size field *= 1-byte), values are stored as ??????
     1925                                        $data = substr($atom_data, $offset, $data_size * 1);
     1926                                        $offset += ($data_size * 1);
     1927                                        break;
     1928                                case 0x0008: // 0x0008 = ?????  (size field *= 2-byte), values are stored as ??????
     1929                                        $data = substr($atom_data, $offset, $data_size * 2);
     1930                                        $offset += ($data_size * 2);
     1931                                        break;
     1932                                default:
     1933echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';
     1934                                        break 2;
     1935                        }
     1936
     1937                        switch ($record_type) {
     1938                                case 0x00000011: // CreateDate
     1939                                case 0x00000012: // DateTimeOriginal
     1940                                        $data = strtotime($data);
     1941                                        break;
     1942                                case 0x0200001e: // ColorSpace
     1943                                        switch ($data) {
     1944                                                case 1:
     1945                                                        $data = 'sRGB';
     1946                                                        break;
     1947                                                case 2:
     1948                                                        $data = 'Adobe RGB';
     1949                                                        break;
     1950                                        }
     1951                                        break;
     1952                                case 0x02000023: // PictureControlData
     1953                                        $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
     1954                                        $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange',    0x83=>'red', 0x84=>'green',  0xff=>'n/a');
     1955                                        $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia',  0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a');
     1956                                        $data = array(
     1957                                                'PictureControlVersion'     =>                           substr($data,  0,  4),
     1958                                                'PictureControlName'        =>                     rtrim(substr($data,  4, 20), "\x00"),
     1959                                                'PictureControlBase'        =>                     rtrim(substr($data, 24, 20), "\x00"),
     1960                                                //'?'                       =>                           substr($data, 44,  4),
     1961                                                'PictureControlAdjust'      => $PictureControlAdjust[ord(substr($data, 48,  1))],
     1962                                                'PictureControlQuickAdjust' =>                       ord(substr($data, 49,  1)),
     1963                                                'Sharpness'                 =>                       ord(substr($data, 50,  1)),
     1964                                                'Contrast'                  =>                       ord(substr($data, 51,  1)),
     1965                                                'Brightness'                =>                       ord(substr($data, 52,  1)),
     1966                                                'Saturation'                =>                       ord(substr($data, 53,  1)),
     1967                                                'HueAdjustment'             =>                       ord(substr($data, 54,  1)),
     1968                                                'FilterEffect'              =>         $FilterEffect[ord(substr($data, 55,  1))],
     1969                                                'ToningEffect'              =>         $ToningEffect[ord(substr($data, 56,  1))],
     1970                                                'ToningSaturation'          =>                       ord(substr($data, 57,  1)),
     1971                                        );
     1972                                        break;
     1973                                case 0x02000024: // WorldTime
     1974                                        // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime
     1975                                        // timezone is stored as offset from GMT in minutes
     1976                                        $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
     1977                                        if ($timezone & 0x8000) {
     1978                                                $timezone = 0 - (0x10000 - $timezone);
     1979                                        }
     1980                                        $timezone /= 60;
     1981
     1982                                        $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1));
     1983                                        switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
     1984                                                case 2:
     1985                                                        $datedisplayformat = 'D/M/Y'; break;
     1986                                                case 1:
     1987                                                        $datedisplayformat = 'M/D/Y'; break;
     1988                                                case 0:
     1989                                                default:
     1990                                                        $datedisplayformat = 'Y/M/D'; break;
     1991                                        }
     1992
     1993                                        $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
     1994                                        break;
     1995                                case 0x02000083: // LensType
     1996                                        $data = array(
     1997                                                //'_'  => $data,
     1998                                                'mf' => (bool) ($data & 0x01),
     1999                                                'd'  => (bool) ($data & 0x02),
     2000                                                'g'  => (bool) ($data & 0x04),
     2001                                                'vr' => (bool) ($data & 0x08),
     2002                                        );
     2003                                        break;
     2004                        }
     2005                        $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
     2006                        $parsed[$tag_name] = $data;
     2007                }
     2008                return $parsed;
     2009        }
     2010
     2011
     2012        public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
     2013                static $handyatomtranslatorarray = array();
     2014                if (empty($handyatomtranslatorarray)) {
     2015                        $handyatomtranslatorarray['©cpy'] = 'copyright';
     2016                        $handyatomtranslatorarray['©day'] = 'creation_date';    // iTunes 4.0
     2017                        $handyatomtranslatorarray['©dir'] = 'director';
     2018                        $handyatomtranslatorarray['©ed1'] = 'edit1';
     2019                        $handyatomtranslatorarray['©ed2'] = 'edit2';
     2020                        $handyatomtranslatorarray['©ed3'] = 'edit3';
     2021                        $handyatomtranslatorarray['©ed4'] = 'edit4';
     2022                        $handyatomtranslatorarray['©ed5'] = 'edit5';
     2023                        $handyatomtranslatorarray['©ed6'] = 'edit6';
     2024                        $handyatomtranslatorarray['©ed7'] = 'edit7';
     2025                        $handyatomtranslatorarray['©ed8'] = 'edit8';
     2026                        $handyatomtranslatorarray['©ed9'] = 'edit9';
     2027                        $handyatomtranslatorarray['©fmt'] = 'format';
     2028                        $handyatomtranslatorarray['©inf'] = 'information';
     2029                        $handyatomtranslatorarray['©prd'] = 'producer';
     2030                        $handyatomtranslatorarray['©prf'] = 'performers';
     2031                        $handyatomtranslatorarray['©req'] = 'system_requirements';
     2032                        $handyatomtranslatorarray['©src'] = 'source_credit';
     2033                        $handyatomtranslatorarray['©wrt'] = 'writer';
     2034
     2035                        // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
     2036                        $handyatomtranslatorarray['©nam'] = 'title';           // iTunes 4.0
     2037                        $handyatomtranslatorarray['©cmt'] = 'comment';         // iTunes 4.0
     2038                        $handyatomtranslatorarray['©wrn'] = 'warning';
     2039                        $handyatomtranslatorarray['©hst'] = 'host_computer';
     2040                        $handyatomtranslatorarray['©mak'] = 'make';
     2041                        $handyatomtranslatorarray['©mod'] = 'model';
     2042                        $handyatomtranslatorarray['©PRD'] = 'product';
     2043                        $handyatomtranslatorarray['©swr'] = 'software';
     2044                        $handyatomtranslatorarray['©aut'] = 'author';
     2045                        $handyatomtranslatorarray['©ART'] = 'artist';
     2046                        $handyatomtranslatorarray['©trk'] = 'track';
     2047                        $handyatomtranslatorarray['©alb'] = 'album';           // iTunes 4.0
     2048                        $handyatomtranslatorarray['©com'] = 'comment';
     2049                        $handyatomtranslatorarray['©gen'] = 'genre';           // iTunes 4.0
     2050                        $handyatomtranslatorarray['©ope'] = 'composer';
     2051                        $handyatomtranslatorarray['©url'] = 'url';
     2052                        $handyatomtranslatorarray['©enc'] = 'encoder';
     2053
     2054                        // http://atomicparsley.sourceforge.net/mpeg-4files.html
     2055                        $handyatomtranslatorarray['©art'] = 'artist';           // iTunes 4.0
     2056                        $handyatomtranslatorarray['aART'] = 'album_artist';
     2057                        $handyatomtranslatorarray['trkn'] = 'track_number';     // iTunes 4.0
     2058                        $handyatomtranslatorarray['disk'] = 'disc_number';      // iTunes 4.0
     2059                        $handyatomtranslatorarray['gnre'] = 'genre';            // iTunes 4.0
     2060                        $handyatomtranslatorarray['©too'] = 'encoder';          // iTunes 4.0
     2061                        $handyatomtranslatorarray['tmpo'] = 'bpm';              // iTunes 4.0
     2062                        $handyatomtranslatorarray['cprt'] = 'copyright';        // iTunes 4.0?
     2063                        $handyatomtranslatorarray['cpil'] = 'compilation';      // iTunes 4.0
     2064                        $handyatomtranslatorarray['covr'] = 'picture';          // iTunes 4.0
     2065                        $handyatomtranslatorarray['rtng'] = 'rating';           // iTunes 4.0
     2066                        $handyatomtranslatorarray['©grp'] = 'grouping';         // iTunes 4.2
     2067                        $handyatomtranslatorarray['stik'] = 'stik';             // iTunes 4.9
     2068                        $handyatomtranslatorarray['pcst'] = 'podcast';          // iTunes 4.9
     2069                        $handyatomtranslatorarray['catg'] = 'category';         // iTunes 4.9
     2070                        $handyatomtranslatorarray['keyw'] = 'keyword';          // iTunes 4.9
     2071                        $handyatomtranslatorarray['purl'] = 'podcast_url';      // iTunes 4.9
     2072                        $handyatomtranslatorarray['egid'] = 'episode_guid';     // iTunes 4.9
     2073                        $handyatomtranslatorarray['desc'] = 'description';      // iTunes 5.0
     2074                        $handyatomtranslatorarray['©lyr'] = 'lyrics';           // iTunes 5.0
     2075                        $handyatomtranslatorarray['tvnn'] = 'tv_network_name';  // iTunes 6.0
     2076                        $handyatomtranslatorarray['tvsh'] = 'tv_show_name';     // iTunes 6.0
     2077                        $handyatomtranslatorarray['tvsn'] = 'tv_season';        // iTunes 6.0
     2078                        $handyatomtranslatorarray['tves'] = 'tv_episode';       // iTunes 6.0
     2079                        $handyatomtranslatorarray['purd'] = 'purchase_date';    // iTunes 6.0.2
     2080                        $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
     2081
     2082                        // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
     2083
     2084
     2085
     2086                        // boxnames:
     2087                        /*
     2088                        $handyatomtranslatorarray['iTunSMPB']                    = 'iTunSMPB';
     2089                        $handyatomtranslatorarray['iTunNORM']                    = 'iTunNORM';
     2090                        $handyatomtranslatorarray['Encoding Params']             = 'Encoding Params';
     2091                        $handyatomtranslatorarray['replaygain_track_gain']       = 'replaygain_track_gain';
     2092                        $handyatomtranslatorarray['replaygain_track_peak']       = 'replaygain_track_peak';
     2093                        $handyatomtranslatorarray['replaygain_track_minmax']     = 'replaygain_track_minmax';
     2094                        $handyatomtranslatorarray['MusicIP PUID']                = 'MusicIP PUID';
     2095                        $handyatomtranslatorarray['MusicBrainz Artist Id']       = 'MusicBrainz Artist Id';
     2096                        $handyatomtranslatorarray['MusicBrainz Album Id']        = 'MusicBrainz Album Id';
     2097                        $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id';
     2098                        $handyatomtranslatorarray['MusicBrainz Track Id']        = 'MusicBrainz Track Id';
     2099                        $handyatomtranslatorarray['MusicBrainz Disc Id']         = 'MusicBrainz Disc Id';
     2100
     2101                        // http://age.hobba.nl/audio/tag_frame_reference.html
     2102                        $handyatomtranslatorarray['PLAY_COUNTER']                = 'play_counter'; // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355
     2103                        $handyatomtranslatorarray['MEDIATYPE']                   = 'mediatype';    // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355
     2104                        */
     2105                }
     2106                $info = &$this->getid3->info;
     2107                $comment_key = '';
     2108                if ($boxname && ($boxname != $keyname)) {
     2109                        $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname);
     2110                } elseif (isset($handyatomtranslatorarray[$keyname])) {
     2111                        $comment_key = $handyatomtranslatorarray[$keyname];
     2112                }
     2113                if ($comment_key) {
     2114                        if ($comment_key == 'picture') {
     2115                                if (!is_array($data)) {
     2116                                        $image_mime = '';
     2117                                        if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) {
     2118                                                $image_mime = 'image/png';
     2119                                        } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) {
     2120                                                $image_mime = 'image/jpeg';
     2121                                        } elseif (preg_match('#^GIF#', $data)) {
     2122                                                $image_mime = 'image/gif';
     2123                                        } elseif (preg_match('#^BM#', $data)) {
     2124                                                $image_mime = 'image/bmp';
     2125                                        }
     2126                                        $data = array('data'=>$data, 'image_mime'=>$image_mime);
     2127                                }
     2128                        }
     2129                        $info['quicktime']['comments'][$comment_key][] = $data;
     2130                }
     2131                return true;
     2132        }
     2133
     2134        public function NoNullString($nullterminatedstring) {
     2135                // remove the single null terminator on null terminated strings
     2136                if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
     2137                        return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
     2138                }
     2139                return $nullterminatedstring;
     2140        }
     2141
     2142        public function Pascal2String($pascalstring) {
     2143                // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
     2144                return substr($pascalstring, 1);
     2145        }
  • new file wp-includes/ID3/module.audio-video.riff.php

    +
    +}
    diff --git wp-includes/ID3/module.audio-video.riff.php wp-includes/ID3/module.audio-video.riff.php
    new file mode 100644
    index 0000000..8f43100
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio-video.riff.php                                 //
     11// module for analyzing RIFF files                             //
     12// multiple formats supported by this module:                  //
     13//    Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX   //
     14// dependencies: module.audio.mp3.php                          //
     15//               module.audio.ac3.php                          //
     16//               module.audio.dts.php                          //
     17//                                                            ///
     18/////////////////////////////////////////////////////////////////
     19
     20/**
     21* @todo Parse AC-3/DTS audio inside WAVE correctly
     22* @todo Rewrite RIFF parser totally
     23*/
     24
     25getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
     26getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
     27getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true);
     28
     29class getid3_riff extends getid3_handler
     30{
     31
     32        public function Analyze() {
     33                $info = &$this->getid3->info;
     34
     35                // initialize these values to an empty array, otherwise they default to NULL
     36                // and you can't append array values to a NULL value
     37                $info['riff'] = array('raw'=>array());
     38
     39                // Shortcuts
     40                $thisfile_riff             = &$info['riff'];
     41                $thisfile_riff_raw         = &$thisfile_riff['raw'];
     42                $thisfile_audio            = &$info['audio'];
     43                $thisfile_video            = &$info['video'];
     44                $thisfile_audio_dataformat = &$thisfile_audio['dataformat'];
     45                $thisfile_riff_audio       = &$thisfile_riff['audio'];
     46                $thisfile_riff_video       = &$thisfile_riff['video'];
     47
     48                $Original['avdataoffset'] = $info['avdataoffset'];
     49                $Original['avdataend']    = $info['avdataend'];
     50
     51                $this->fseek($info['avdataoffset']);
     52                $RIFFheader = $this->fread(12);
     53                $offset = $this->ftell();
     54                $RIFFtype    = substr($RIFFheader, 0, 4);
     55                $RIFFsize    = substr($RIFFheader, 4, 4);
     56                $RIFFsubtype = substr($RIFFheader, 8, 4);
     57
     58                switch ($RIFFtype) {
     59
     60                        case 'FORM':  // AIFF, AIFC
     61                                $info['fileformat']   = 'aiff';
     62                                $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize);
     63                                $thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
     64                                break;
     65
     66                        case 'RIFF':  // AVI, WAV, etc
     67                        case 'SDSS':  // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com)
     68                        case 'RMP3':  // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s
     69                                $info['fileformat']   = 'riff';
     70                                $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize);
     71                                if ($RIFFsubtype == 'RMP3') {
     72                                        // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s
     73                                        $RIFFsubtype = 'WAVE';
     74                                }
     75                                $thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
     76                                if (($info['avdataend'] - $info['filesize']) == 1) {
     77                                        // LiteWave appears to incorrectly *not* pad actual output file
     78                                        // to nearest WORD boundary so may appear to be short by one
     79                                        // byte, in which case - skip warning
     80                                        $info['avdataend'] = $info['filesize'];
     81                                }
     82
     83                                $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset
     84                                while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) {
     85                                        try {
     86                                                $this->fseek($nextRIFFoffset);
     87                                        } catch (getid3_exception $e) {
     88                                                if ($e->getCode() == 10) {
     89                                                        //$this->warning('RIFF parser: '.$e->getMessage());
     90                                                        $this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong');
     91                                                        $this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present');
     92                                                        break;
     93                                                } else {
     94                                                        throw $e;
     95                                                }
     96                                        }
     97                                        $nextRIFFheader = $this->fread(12);
     98                                        if ($nextRIFFoffset == ($info['avdataend'] - 1)) {
     99                                                if (substr($nextRIFFheader, 0, 1) == "\x00") {
     100                                                        // RIFF padded to WORD boundary, we're actually already at the end
     101                                                        break;
     102                                                }
     103                                        }
     104                                        $nextRIFFheaderID =                         substr($nextRIFFheader, 0, 4);
     105                                        $nextRIFFsize     = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4));
     106                                        $nextRIFFtype     =                         substr($nextRIFFheader, 8, 4);
     107                                        $chunkdata = array();
     108                                        $chunkdata['offset'] = $nextRIFFoffset + 8;
     109                                        $chunkdata['size']   = $nextRIFFsize;
     110                                        $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size'];
     111
     112                                        switch ($nextRIFFheaderID) {
     113
     114                                                case 'RIFF':
     115                                                        $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset);
     116
     117                                                        if (!isset($thisfile_riff[$nextRIFFtype])) {
     118                                                                $thisfile_riff[$nextRIFFtype] = array();
     119                                                        }
     120                                                        $thisfile_riff[$nextRIFFtype][] = $chunkdata;
     121                                                        break;
     122
     123                                                case 'JUNK':
     124                                                        // ignore
     125                                                        $thisfile_riff[$nextRIFFheaderID][] = $chunkdata;
     126                                                        break;
     127
     128                                                case 'IDVX':
     129                                                        $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size']));
     130                                                        break;
     131
     132                                                default:
     133                                                        if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) {
     134                                                                $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12);
     135                                                                if (substr($DIVXTAG, -7) == 'DIVXTAG') {
     136                                                                        // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file
     137                                                                        $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway');
     138                                                                        $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG);
     139                                                                        break 2;
     140                                                                }
     141                                                        }
     142                                                        $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file');
     143                                                        break 2;
     144
     145                                        }
     146
     147                                }
     148                                if ($RIFFsubtype == 'WAVE') {
     149                                        $thisfile_riff_WAVE = &$thisfile_riff['WAVE'];
     150                                }
     151                                break;
     152
     153                        default:
     154                                $this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead');
     155                                unset($info['fileformat']);
     156                                return false;
     157                }
     158
     159                $streamindex = 0;
     160                switch ($RIFFsubtype) {
     161                        case 'WAVE':
     162                                if (empty($thisfile_audio['bitrate_mode'])) {
     163                                        $thisfile_audio['bitrate_mode'] = 'cbr';
     164                                }
     165                                if (empty($thisfile_audio_dataformat)) {
     166                                        $thisfile_audio_dataformat = 'wav';
     167                                }
     168
     169                                if (isset($thisfile_riff_WAVE['data'][0]['offset'])) {
     170                                        $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8;
     171                                        $info['avdataend']    = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size'];
     172                                }
     173                                if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) {
     174
     175                                        $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']);
     176                                        $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag'];
     177                                        if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) {
     178                                                $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero';
     179                                                return false;
     180                                        }
     181                                        $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw'];
     182                                        unset($thisfile_riff_audio[$streamindex]['raw']);
     183                                        $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex];
     184
     185                                        $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]);
     186                                        if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') {
     187                                                $info['warning'][] = 'Audio codec = '.$thisfile_audio['codec'];
     188                                        }
     189                                        $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
     190
     191                                        if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV)
     192                                                $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']);
     193                                        }
     194
     195                                        $thisfile_audio['lossless'] = false;
     196                                        if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) {
     197                                                switch ($thisfile_riff_raw['fmt ']['wFormatTag']) {
     198
     199                                                        case 0x0001:  // PCM
     200                                                                $thisfile_audio['lossless'] = true;
     201                                                                break;
     202
     203                                                        case 0x2000:  // AC-3
     204                                                                $thisfile_audio_dataformat = 'ac3';
     205                                                                break;
     206
     207                                                        default:
     208                                                                // do nothing
     209                                                                break;
     210
     211                                                }
     212                                        }
     213                                        $thisfile_audio['streams'][$streamindex]['wformattag']   = $thisfile_audio['wformattag'];
     214                                        $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
     215                                        $thisfile_audio['streams'][$streamindex]['lossless']     = $thisfile_audio['lossless'];
     216                                        $thisfile_audio['streams'][$streamindex]['dataformat']   = $thisfile_audio_dataformat;
     217                                }
     218
     219                                if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) {
     220
     221                                        // shortcuts
     222                                        $rgadData = &$thisfile_riff_WAVE['rgad'][0]['data'];
     223                                        $thisfile_riff_raw['rgad']    = array('track'=>array(), 'album'=>array());
     224                                        $thisfile_riff_raw_rgad       = &$thisfile_riff_raw['rgad'];
     225                                        $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track'];
     226                                        $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album'];
     227
     228                                        $thisfile_riff_raw_rgad['fPeakAmplitude']      = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4));
     229                                        $thisfile_riff_raw_rgad['nRadioRgAdjust']      =        $this->EitherEndian2Int(substr($rgadData, 4, 2));
     230                                        $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] =        $this->EitherEndian2Int(substr($rgadData, 6, 2));
     231
     232                                        $nRadioRgAdjustBitstring      = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT);
     233                                        $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT);
     234                                        $thisfile_riff_raw_rgad_track['name']       = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3));
     235                                        $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3));
     236                                        $thisfile_riff_raw_rgad_track['signbit']    = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1));
     237                                        $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9));
     238                                        $thisfile_riff_raw_rgad_album['name']       = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3));
     239                                        $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3));
     240                                        $thisfile_riff_raw_rgad_album['signbit']    = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1));
     241                                        $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9));
     242
     243                                        $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude'];
     244                                        if (($thisfile_riff_raw_rgad_track['name'] != 0) && ($thisfile_riff_raw_rgad_track['originator'] != 0)) {
     245                                                $thisfile_riff['rgad']['track']['name']            = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']);
     246                                                $thisfile_riff['rgad']['track']['originator']      = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']);
     247                                                $thisfile_riff['rgad']['track']['adjustment']      = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']);
     248                                        }
     249                                        if (($thisfile_riff_raw_rgad_album['name'] != 0) && ($thisfile_riff_raw_rgad_album['originator'] != 0)) {
     250                                                $thisfile_riff['rgad']['album']['name']       = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']);
     251                                                $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']);
     252                                                $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']);
     253                                        }
     254                                }
     255
     256                                if (isset($thisfile_riff_WAVE['fact'][0]['data'])) {
     257                                        $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4));
     258
     259                                        // This should be a good way of calculating exact playtime,
     260                                        // but some sample files have had incorrect number of samples,
     261                                        // so cannot use this method
     262
     263                                        // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) {
     264                                        //     $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec'];
     265                                        // }
     266                                }
     267                                if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) {
     268                                        $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8);
     269                                }
     270
     271                                if (isset($thisfile_riff_WAVE['bext'][0]['data'])) {
     272                                        // shortcut
     273                                        $thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0];
     274
     275                                        $thisfile_riff_WAVE_bext_0['title']          =                         trim(substr($thisfile_riff_WAVE_bext_0['data'],   0, 256));
     276                                        $thisfile_riff_WAVE_bext_0['author']         =                         trim(substr($thisfile_riff_WAVE_bext_0['data'], 256,  32));
     277                                        $thisfile_riff_WAVE_bext_0['reference']      =                         trim(substr($thisfile_riff_WAVE_bext_0['data'], 288,  32));
     278                                        $thisfile_riff_WAVE_bext_0['origin_date']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 320,  10);
     279                                        $thisfile_riff_WAVE_bext_0['origin_time']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 330,   8);
     280                                        $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338,   8));
     281                                        $thisfile_riff_WAVE_bext_0['bwf_version']    = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346,   1));
     282                                        $thisfile_riff_WAVE_bext_0['reserved']       =                              substr($thisfile_riff_WAVE_bext_0['data'], 347, 254);
     283                                        $thisfile_riff_WAVE_bext_0['coding_history'] =         explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601)));
     284                                        if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) {
     285                                                if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) {
     286                                                        list($dummy, $bext_timestamp['year'], $bext_timestamp['month'],  $bext_timestamp['day'])    = $matches_bext_date;
     287                                                        list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time;
     288                                                        $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']);
     289                                                } else {
     290                                                        $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid';
     291                                                }
     292                                        } else {
     293                                                $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid';
     294                                        }
     295                                        $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author'];
     296                                        $thisfile_riff['comments']['title'][]  = $thisfile_riff_WAVE_bext_0['title'];
     297                                }
     298
     299                                if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) {
     300                                        // shortcut
     301                                        $thisfile_riff_WAVE_MEXT_0 = &$thisfile_riff_WAVE['MEXT'][0];
     302
     303                                        $thisfile_riff_WAVE_MEXT_0['raw']['sound_information']      = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2));
     304                                        $thisfile_riff_WAVE_MEXT_0['flags']['homogenous']           = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0001);
     305                                        if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) {
     306                                                $thisfile_riff_WAVE_MEXT_0['flags']['padding']          = ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0002) ? false : true;
     307                                                $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44']         =        (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0004);
     308                                                $thisfile_riff_WAVE_MEXT_0['flags']['free_format']      =        (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0008);
     309
     310                                                $thisfile_riff_WAVE_MEXT_0['nominal_frame_size']        = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2));
     311                                        }
     312                                        $thisfile_riff_WAVE_MEXT_0['anciliary_data_length']         = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2));
     313                                        $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def']     = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2));
     314                                        $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left']  = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0001);
     315                                        $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free']  = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0002);
     316                                        $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0004);
     317                                }
     318
     319                                if (isset($thisfile_riff_WAVE['cart'][0]['data'])) {
     320                                        // shortcut
     321                                        $thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0];
     322
     323                                        $thisfile_riff_WAVE_cart_0['version']              =                              substr($thisfile_riff_WAVE_cart_0['data'],   0,  4);
     324                                        $thisfile_riff_WAVE_cart_0['title']                =                         trim(substr($thisfile_riff_WAVE_cart_0['data'],   4, 64));
     325                                        $thisfile_riff_WAVE_cart_0['artist']               =                         trim(substr($thisfile_riff_WAVE_cart_0['data'],  68, 64));
     326                                        $thisfile_riff_WAVE_cart_0['cut_id']               =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64));
     327                                        $thisfile_riff_WAVE_cart_0['client_id']            =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64));
     328                                        $thisfile_riff_WAVE_cart_0['category']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64));
     329                                        $thisfile_riff_WAVE_cart_0['classification']       =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64));
     330                                        $thisfile_riff_WAVE_cart_0['out_cue']              =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64));
     331                                        $thisfile_riff_WAVE_cart_0['start_date']           =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10));
     332                                        $thisfile_riff_WAVE_cart_0['start_time']           =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 462,  8));
     333                                        $thisfile_riff_WAVE_cart_0['end_date']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10));
     334                                        $thisfile_riff_WAVE_cart_0['end_time']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 480,  8));
     335                                        $thisfile_riff_WAVE_cart_0['producer_app_id']      =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64));
     336                                        $thisfile_riff_WAVE_cart_0['producer_app_version'] =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64));
     337                                        $thisfile_riff_WAVE_cart_0['user_defined_text']    =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64));
     338                                        $thisfile_riff_WAVE_cart_0['zero_db_reference']    = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680,  4), true);
     339                                        for ($i = 0; $i < 8; $i++) {
     340                                                $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] =                  substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4);
     341                                                $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value']  = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4));
     342                                        }
     343                                        $thisfile_riff_WAVE_cart_0['url']              =                 trim(substr($thisfile_riff_WAVE_cart_0['data'],  748, 1024));
     344                                        $thisfile_riff_WAVE_cart_0['tag_text']         = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772)));
     345
     346                                        $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist'];
     347                                        $thisfile_riff['comments']['title'][]  = $thisfile_riff_WAVE_cart_0['title'];
     348                                }
     349
     350                                if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) {
     351                                        // SoundMiner metadata
     352
     353                                        // shortcuts
     354                                        $thisfile_riff_WAVE_SNDM_0      = &$thisfile_riff_WAVE['SNDM'][0];
     355                                        $thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data'];
     356                                        $SNDM_startoffset = 0;
     357                                        $SNDM_endoffset   = $thisfile_riff_WAVE_SNDM_0['size'];
     358
     359                                        while ($SNDM_startoffset < $SNDM_endoffset) {
     360                                                $SNDM_thisTagOffset = 0;
     361                                                $SNDM_thisTagSize      = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4));
     362                                                $SNDM_thisTagOffset += 4;
     363                                                $SNDM_thisTagKey       =                           substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4);
     364                                                $SNDM_thisTagOffset += 4;
     365                                                $SNDM_thisTagDataSize  = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2));
     366                                                $SNDM_thisTagOffset += 2;
     367                                                $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2));
     368                                                $SNDM_thisTagOffset += 2;
     369                                                $SNDM_thisTagDataText =                            substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize);
     370                                                $SNDM_thisTagOffset += $SNDM_thisTagDataSize;
     371
     372                                                if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) {
     373                                                        $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
     374                                                        break;
     375                                                } elseif ($SNDM_thisTagSize <= 0) {
     376                                                        $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
     377                                                        break;
     378                                                }
     379                                                $SNDM_startoffset += $SNDM_thisTagSize;
     380
     381                                                $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText;
     382                                                if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) {
     383                                                        $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText;
     384                                                } else {
     385                                                        $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
     386                                                }
     387                                        }
     388
     389                                        $tagmapping = array(
     390                                                'tracktitle'=>'title',
     391                                                'category'  =>'genre',
     392                                                'cdtitle'   =>'album',
     393                                                'tracktitle'=>'title',
     394                                        );
     395                                        foreach ($tagmapping as $fromkey => $tokey) {
     396                                                if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) {
     397                                                        $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey];
     398                                                }
     399                                        }
     400                                }
     401
     402                                if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) {
     403                                        // requires functions simplexml_load_string and get_object_vars
     404                                        if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) {
     405                                                $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML;
     406                                                if (isset($parsedXML['SPEED']['MASTER_SPEED'])) {
     407                                                        @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']);
     408                                                        $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000);
     409                                                }
     410                                                if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) {
     411                                                        @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']);
     412                                                        $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000);
     413                                                }
     414                                                if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) {
     415                                                        $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0'));
     416                                                        $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE'];
     417                                                        $h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds']       / 3600);
     418                                                        $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600))      / 60);
     419                                                        $s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60));
     420                                                        $f =       ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate'];
     421                                                        $thisfile_riff_WAVE['iXML'][0]['timecode_string']       = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s,       $f);
     422                                                        $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d',   $h, $m, $s, round($f));
     423                                                }
     424                                                unset($parsedXML);
     425                                        }
     426                                }
     427
     428
     429
     430                                if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) {
     431                                        $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
     432                                        $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']);
     433                                }
     434
     435                                if (!empty($info['wavpack'])) {
     436                                        $thisfile_audio_dataformat = 'wavpack';
     437                                        $thisfile_audio['bitrate_mode'] = 'vbr';
     438                                        $thisfile_audio['encoder']      = 'WavPack v'.$info['wavpack']['version'];
     439
     440                                        // Reset to the way it was - RIFF parsing will have messed this up
     441                                        $info['avdataend']        = $Original['avdataend'];
     442                                        $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
     443
     444                                        $this->fseek($info['avdataoffset'] - 44);
     445                                        $RIFFdata = $this->fread(44);
     446                                        $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata,  4, 4)) +  8;
     447                                        $OrignalRIFFdataSize   = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;
     448
     449                                        if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
     450                                                $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
     451                                                $this->fseek($info['avdataend']);
     452                                                $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
     453                                        }
     454
     455                                        // move the data chunk after all other chunks (if any)
     456                                        // so that the RIFF parser doesn't see EOF when trying
     457                                        // to skip over the data chunk
     458                                        $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
     459                                        $getid3_riff = new getid3_riff($this->getid3);
     460                                        $getid3_riff->ParseRIFFdata($RIFFdata);
     461                                        unset($getid3_riff);
     462                                }
     463
     464                                if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) {
     465                                        switch ($thisfile_riff_raw['fmt ']['wFormatTag']) {
     466                                                case 0x0001: // PCM
     467                                                        if (!empty($info['ac3'])) {
     468                                                                // Dolby Digital WAV files masquerade as PCM-WAV, but they're not
     469                                                                $thisfile_audio['wformattag']  = 0x2000;
     470                                                                $thisfile_audio['codec']       = self::wFormatTagLookup($thisfile_audio['wformattag']);
     471                                                                $thisfile_audio['lossless']    = false;
     472                                                                $thisfile_audio['bitrate']     = $info['ac3']['bitrate'];
     473                                                                $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate'];
     474                                                        }
     475                                                        if (!empty($info['dts'])) {
     476                                                                // Dolby DTS files masquerade as PCM-WAV, but they're not
     477                                                                $thisfile_audio['wformattag']  = 0x2001;
     478                                                                $thisfile_audio['codec']       = self::wFormatTagLookup($thisfile_audio['wformattag']);
     479                                                                $thisfile_audio['lossless']    = false;
     480                                                                $thisfile_audio['bitrate']     = $info['dts']['bitrate'];
     481                                                                $thisfile_audio['sample_rate'] = $info['dts']['sample_rate'];
     482                                                        }
     483                                                        break;
     484                                                case 0x08AE: // ClearJump LiteWave
     485                                                        $thisfile_audio['bitrate_mode'] = 'vbr';
     486                                                        $thisfile_audio_dataformat   = 'litewave';
     487
     488                                                        //typedef struct tagSLwFormat {
     489                                                        //  WORD    m_wCompFormat;     // low byte defines compression method, high byte is compression flags
     490                                                        //  DWORD   m_dwScale;         // scale factor for lossy compression
     491                                                        //  DWORD   m_dwBlockSize;     // number of samples in encoded blocks
     492                                                        //  WORD    m_wQuality;        // alias for the scale factor
     493                                                        //  WORD    m_wMarkDistance;   // distance between marks in bytes
     494                                                        //  WORD    m_wReserved;
     495                                                        //
     496                                                        //  //following paramters are ignored if CF_FILESRC is not set
     497                                                        //  DWORD   m_dwOrgSize;       // original file size in bytes
     498                                                        //  WORD    m_bFactExists;     // indicates if 'fact' chunk exists in the original file
     499                                                        //  DWORD   m_dwRiffChunkSize; // riff chunk size in the original file
     500                                                        //
     501                                                        //  PCMWAVEFORMAT m_OrgWf;     // original wave format
     502                                                        // }SLwFormat, *PSLwFormat;
     503
     504                                                        // shortcut
     505                                                        $thisfile_riff['litewave']['raw'] = array();
     506                                                        $riff_litewave     = &$thisfile_riff['litewave'];
     507                                                        $riff_litewave_raw = &$riff_litewave['raw'];
     508
     509                                                        $flags = array(
     510                                                                'compression_method' => 1,
     511                                                                'compression_flags'  => 1,
     512                                                                'm_dwScale'          => 4,
     513                                                                'm_dwBlockSize'      => 4,
     514                                                                'm_wQuality'         => 2,
     515                                                                'm_wMarkDistance'    => 2,
     516                                                                'm_wReserved'        => 2,
     517                                                                'm_dwOrgSize'        => 4,
     518                                                                'm_bFactExists'      => 2,
     519                                                                'm_dwRiffChunkSize'  => 4,
     520                                                        );
     521                                                        $litewave_offset = 18;
     522                                                        foreach ($flags as $flag => $length) {
     523                                                                $riff_litewave_raw[$flag] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], $litewave_offset, $length));
     524                                                                $litewave_offset += $length;
     525                                                        }
     526
     527                                                        //$riff_litewave['quality_factor'] = intval(round((2000 - $riff_litewave_raw['m_dwScale']) / 20));
     528                                                        $riff_litewave['quality_factor'] = $riff_litewave_raw['m_wQuality'];
     529
     530                                                        $riff_litewave['flags']['raw_source']    = ($riff_litewave_raw['compression_flags'] & 0x01) ? false : true;
     531                                                        $riff_litewave['flags']['vbr_blocksize'] = ($riff_litewave_raw['compression_flags'] & 0x02) ? false : true;
     532                                                        $riff_litewave['flags']['seekpoints']    =        (bool) ($riff_litewave_raw['compression_flags'] & 0x04);
     533
     534                                                        $thisfile_audio['lossless']        = (($riff_litewave_raw['m_wQuality'] == 100) ? true : false);
     535                                                        $thisfile_audio['encoder_options'] = '-q'.$riff_litewave['quality_factor'];
     536                                                        break;
     537
     538                                                default:
     539                                                        break;
     540                                        }
     541                                }
     542                                if ($info['avdataend'] > $info['filesize']) {
     543                                        switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') {
     544                                                case 'wavpack': // WavPack
     545                                                case 'lpac':    // LPAC
     546                                                case 'ofr':     // OptimFROG
     547                                                case 'ofs':     // OptimFROG DualStream
     548                                                        // lossless compressed audio formats that keep original RIFF headers - skip warning
     549                                                        break;
     550
     551                                                case 'litewave':
     552                                                        if (($info['avdataend'] - $info['filesize']) == 1) {
     553                                                                // LiteWave appears to incorrectly *not* pad actual output file
     554                                                                // to nearest WORD boundary so may appear to be short by one
     555                                                                // byte, in which case - skip warning
     556                                                        } else {
     557                                                                // Short by more than one byte, throw warning
     558                                                                $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
     559                                                                $info['avdataend'] = $info['filesize'];
     560                                                        }
     561                                                        break;
     562
     563                                                default:
     564                                                        if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) {
     565                                                                // output file appears to be incorrectly *not* padded to nearest WORD boundary
     566                                                                // Output less severe warning
     567                                                                $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
     568                                                                $info['avdataend'] = $info['filesize'];
     569                                                        } else {
     570                                                                // Short by more than one byte, throw warning
     571                                                                $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
     572                                                                $info['avdataend'] = $info['filesize'];
     573                                                        }
     574                                                        break;
     575                                        }
     576                                }
     577                                if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) {
     578                                        if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) {
     579                                                $info['avdataend']--;
     580                                                $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored';
     581                                        }
     582                                }
     583                                if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) {
     584                                        unset($thisfile_audio['bits_per_sample']);
     585                                        if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) {
     586                                                $thisfile_audio['bitrate'] = $info['ac3']['bitrate'];
     587                                        }
     588                                }
     589                                break;
     590
     591                        case 'AVI ':
     592                                $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably
     593                                $thisfile_video['dataformat']   = 'avi';
     594                                $info['mime_type']      = 'video/avi';
     595
     596                                if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) {
     597                                        $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8;
     598                                        if (isset($thisfile_riff['AVIX'])) {
     599                                                $info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size'];
     600                                        } else {
     601                                                $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size'];
     602                                        }
     603                                        if ($info['avdataend'] > $info['filesize']) {
     604                                                $info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)';
     605                                                $info['avdataend'] = $info['filesize'];
     606                                        }
     607                                }
     608
     609                                if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) {
     610                                        //$bIndexType = array(
     611                                        //      0x00 => 'AVI_INDEX_OF_INDEXES',
     612                                        //      0x01 => 'AVI_INDEX_OF_CHUNKS',
     613                                        //      0x80 => 'AVI_INDEX_IS_DATA',
     614                                        //);
     615                                        //$bIndexSubtype = array(
     616                                        //      0x01 => array(
     617                                        //              0x01 => 'AVI_INDEX_2FIELD',
     618                                        //      ),
     619                                        //);
     620                                        foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) {
     621                                                $ahsisd = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data'];
     622
     623                                                $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($ahsisd,  0, 2));
     624                                                $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']  = $this->EitherEndian2Int(substr($ahsisd,  2, 1));
     625                                                $thisfile_riff_raw['indx'][$streamnumber]['bIndexType']     = $this->EitherEndian2Int(substr($ahsisd,  3, 1));
     626                                                $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse']  = $this->EitherEndian2Int(substr($ahsisd,  4, 4));
     627                                                $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId']      =                         substr($ahsisd,  8, 4);
     628                                                $thisfile_riff_raw['indx'][$streamnumber]['dwReserved']     = $this->EitherEndian2Int(substr($ahsisd, 12, 4));
     629
     630                                                //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name']    =    $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']];
     631                                                //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']];
     632
     633                                                unset($ahsisd);
     634                                        }
     635                                }
     636                                if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) {
     637                                        $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'];
     638
     639                                        // shortcut
     640                                        $thisfile_riff_raw['avih'] = array();
     641                                        $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih'];
     642
     643                                        $thisfile_riff_raw_avih['dwMicroSecPerFrame']    = $this->EitherEndian2Int(substr($avihData,  0, 4)); // frame display rate (or 0L)
     644                                        if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) {
     645                                                $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero';
     646                                                return false;
     647                                        }
     648
     649                                        $flags = array(
     650                                                'dwMaxBytesPerSec',       // max. transfer rate
     651                                                'dwPaddingGranularity',   // pad to multiples of this size; normally 2K.
     652                                                'dwFlags',                // the ever-present flags
     653                                                'dwTotalFrames',          // # frames in file
     654                                                'dwInitialFrames',        //
     655                                                'dwStreams',              //
     656                                                'dwSuggestedBufferSize',  //
     657                                                'dwWidth',                //
     658                                                'dwHeight',               //
     659                                                'dwScale',                //
     660                                                'dwRate',                 //
     661                                                'dwStart',                //
     662                                                'dwLength',               //
     663                                        );
     664                                        $avih_offset = 4;
     665                                        foreach ($flags as $flag) {
     666                                                $thisfile_riff_raw_avih[$flag] = $this->EitherEndian2Int(substr($avihData, $avih_offset, 4));
     667                                                $avih_offset += 4;
     668                                        }
     669
     670                                        $flags = array(
     671                                                'hasindex'     => 0x00000010,
     672                                                'mustuseindex' => 0x00000020,
     673                                                'interleaved'  => 0x00000100,
     674                                                'trustcktype'  => 0x00000800,
     675                                                'capturedfile' => 0x00010000,
     676                                                'copyrighted'  => 0x00020010,
     677                                        );
     678                    foreach ($flags as $flag => $value) {
     679                                                $thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value);
     680                                        }
     681
     682                                        // shortcut
     683                                        $thisfile_riff_video[$streamindex] = array();
     684                                        $thisfile_riff_video_current = &$thisfile_riff_video[$streamindex];
     685
     686                                        if ($thisfile_riff_raw_avih['dwWidth'] > 0) {
     687                                                $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth'];
     688                                                $thisfile_video['resolution_x']             = $thisfile_riff_video_current['frame_width'];
     689                                        }
     690                                        if ($thisfile_riff_raw_avih['dwHeight'] > 0) {
     691                                                $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight'];
     692                                                $thisfile_video['resolution_y']              = $thisfile_riff_video_current['frame_height'];
     693                                        }
     694                                        if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) {
     695                                                $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames'];
     696                                                $thisfile_video['total_frames']              = $thisfile_riff_video_current['total_frames'];
     697                                        }
     698
     699                                        $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3);
     700                                        $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate'];
     701                                }
     702                                if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) {
     703                                        if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) {
     704                                                for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) {
     705                                                        if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) {
     706                                                                $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'];
     707                                                                $strhfccType = substr($strhData,  0, 4);
     708
     709                                                                if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) {
     710                                                                        $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'];
     711
     712                                                                        // shortcut
     713                                                                        $thisfile_riff_raw_strf_strhfccType_streamindex = &$thisfile_riff_raw['strf'][$strhfccType][$streamindex];
     714
     715                                                                        switch ($strhfccType) {
     716                                                                                case 'auds':
     717                                                                                        $thisfile_audio['bitrate_mode'] = 'cbr';
     718                                                                                        $thisfile_audio_dataformat      = 'wav';
     719                                                                                        if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) {
     720                                                                                                $streamindex = count($thisfile_riff_audio);
     721                                                                                        }
     722
     723                                                                                        $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($strfData);
     724                                                                                        $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag'];
     725
     726                                                                                        // shortcut
     727                                                                                        $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex];
     728                                                                                        $thisfile_audio_streams_currentstream = &$thisfile_audio['streams'][$streamindex];
     729
     730                                                                                        if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) {
     731                                                                                                unset($thisfile_audio_streams_currentstream['bits_per_sample']);
     732                                                                                        }
     733                                                                                        $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag'];
     734                                                                                        unset($thisfile_audio_streams_currentstream['raw']);
     735
     736                                                                                        // shortcut
     737                                                                                        $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw'];
     738
     739                                                                                        unset($thisfile_riff_audio[$streamindex]['raw']);
     740                                                                                        $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]);
     741
     742                                                                                        $thisfile_audio['lossless'] = false;
     743                                                                                        switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) {
     744                                                                                                case 0x0001:  // PCM
     745                                                                                                        $thisfile_audio_dataformat  = 'wav';
     746                                                                                                        $thisfile_audio['lossless'] = true;
     747                                                                                                        break;
     748
     749                                                                                                case 0x0050: // MPEG Layer 2 or Layer 1
     750                                                                                                        $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2
     751                                                                                                        break;
     752
     753                                                                                                case 0x0055: // MPEG Layer 3
     754                                                                                                        $thisfile_audio_dataformat = 'mp3';
     755                                                                                                        break;
     756
     757                                                                                                case 0x00FF: // AAC
     758                                                                                                        $thisfile_audio_dataformat = 'aac';
     759                                                                                                        break;
     760
     761                                                                                                case 0x0161: // Windows Media v7 / v8 / v9
     762                                                                                                case 0x0162: // Windows Media Professional v9
     763                                                                                                case 0x0163: // Windows Media Lossess v9
     764                                                                                                        $thisfile_audio_dataformat = 'wma';
     765                                                                                                        break;
     766
     767                                                                                                case 0x2000: // AC-3
     768                                                                                                        $thisfile_audio_dataformat = 'ac3';
     769                                                                                                        break;
     770
     771                                                                                                case 0x2001: // DTS
     772                                                                                                        $thisfile_audio_dataformat = 'dts';
     773                                                                                                        break;
     774
     775                                                                                                default:
     776                                                                                                        $thisfile_audio_dataformat = 'wav';
     777                                                                                                        break;
     778                                                                                        }
     779                                                                                        $thisfile_audio_streams_currentstream['dataformat']   = $thisfile_audio_dataformat;
     780                                                                                        $thisfile_audio_streams_currentstream['lossless']     = $thisfile_audio['lossless'];
     781                                                                                        $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
     782                                                                                        break;
     783
     784
     785                                                                                case 'iavs':
     786                                                                                case 'vids':
     787                                                                                        // shortcut
     788                                                                                        $thisfile_riff_raw['strh'][$i]                  = array();
     789                                                                                        $thisfile_riff_raw_strh_current                 = &$thisfile_riff_raw['strh'][$i];
     790
     791                                                                                        $thisfile_riff_raw_strh_current['fccType']               =                         substr($strhData,  0, 4);  // same as $strhfccType;
     792                                                                                        $thisfile_riff_raw_strh_current['fccHandler']            =                         substr($strhData,  4, 4);
     793                                                                                        $thisfile_riff_raw_strh_current['dwFlags']               = $this->EitherEndian2Int(substr($strhData,  8, 4)); // Contains AVITF_* flags
     794                                                                                        $thisfile_riff_raw_strh_current['wPriority']             = $this->EitherEndian2Int(substr($strhData, 12, 2));
     795                                                                                        $thisfile_riff_raw_strh_current['wLanguage']             = $this->EitherEndian2Int(substr($strhData, 14, 2));
     796                                                                                        $thisfile_riff_raw_strh_current['dwInitialFrames']       = $this->EitherEndian2Int(substr($strhData, 16, 4));
     797                                                                                        $thisfile_riff_raw_strh_current['dwScale']               = $this->EitherEndian2Int(substr($strhData, 20, 4));
     798                                                                                        $thisfile_riff_raw_strh_current['dwRate']                = $this->EitherEndian2Int(substr($strhData, 24, 4));
     799                                                                                        $thisfile_riff_raw_strh_current['dwStart']               = $this->EitherEndian2Int(substr($strhData, 28, 4));
     800                                                                                        $thisfile_riff_raw_strh_current['dwLength']              = $this->EitherEndian2Int(substr($strhData, 32, 4));
     801                                                                                        $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4));
     802                                                                                        $thisfile_riff_raw_strh_current['dwQuality']             = $this->EitherEndian2Int(substr($strhData, 40, 4));
     803                                                                                        $thisfile_riff_raw_strh_current['dwSampleSize']          = $this->EitherEndian2Int(substr($strhData, 44, 4));
     804                                                                                        $thisfile_riff_raw_strh_current['rcFrame']               = $this->EitherEndian2Int(substr($strhData, 48, 4));
     805
     806                                                                                        $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strh_current['fccHandler']);
     807                                                                                        $thisfile_video['fourcc']             = $thisfile_riff_raw_strh_current['fccHandler'];
     808                                                                                        if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) {
     809                                                                                                $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']);
     810                                                                                                $thisfile_video['fourcc']             = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'];
     811                                                                                        }
     812                                                                                        $thisfile_video['codec']              = $thisfile_riff_video_current['codec'];
     813                                                                                        $thisfile_video['pixel_aspect_ratio'] = (float) 1;
     814                                                                                        switch ($thisfile_riff_raw_strh_current['fccHandler']) {
     815                                                                                                case 'HFYU': // Huffman Lossless Codec
     816                                                                                                case 'IRAW': // Intel YUV Uncompressed
     817                                                                                                case 'YUY2': // Uncompressed YUV 4:2:2
     818                                                                                                        $thisfile_video['lossless'] = true;
     819                                                                                                        break;
     820
     821                                                                                                default:
     822                                                                                                        $thisfile_video['lossless'] = false;
     823                                                                                                        break;
     824                                                                                        }
     825
     826                                                                                        switch ($strhfccType) {
     827                                                                                                case 'vids':
     828                                                                                                        $thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff'));
     829                                                                                                        $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'];
     830
     831                                                                                                        if ($thisfile_riff_video_current['codec'] == 'DV') {
     832                                                                                                                $thisfile_riff_video_current['dv_type'] = 2;
     833                                                                                                        }
     834                                                                                                        break;
     835
     836                                                                                                case 'iavs':
     837                                                                                                        $thisfile_riff_video_current['dv_type'] = 1;
     838                                                                                                        break;
     839                                                                                        }
     840                                                                                        break;
     841
     842                                                                                default:
     843                                                                                        $info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"';
     844                                                                                        break;
     845
     846                                                                        }
     847                                                                }
     848                                                        }
     849
     850                                                        if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) {
     851
     852                                                                $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'];
     853                                                                if (self::fourccLookup($thisfile_video['fourcc'])) {
     854                                                                        $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_video['fourcc']);
     855                                                                        $thisfile_video['codec']              = $thisfile_riff_video_current['codec'];
     856                                                                }
     857
     858                                                                switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) {
     859                                                                        case 'HFYU': // Huffman Lossless Codec
     860                                                                        case 'IRAW': // Intel YUV Uncompressed
     861                                                                        case 'YUY2': // Uncompressed YUV 4:2:2
     862                                                                                $thisfile_video['lossless']        = true;
     863                                                                                //$thisfile_video['bits_per_sample'] = 24;
     864                                                                                break;
     865
     866                                                                        default:
     867                                                                                $thisfile_video['lossless']        = false;
     868                                                                                //$thisfile_video['bits_per_sample'] = 24;
     869                                                                                break;
     870                                                                }
     871
     872                                                        }
     873                                                }
     874                                        }
     875                                }
     876                                break;
     877
     878                        case 'CDDA':
     879                                $thisfile_audio['bitrate_mode'] = 'cbr';
     880                                $thisfile_audio_dataformat      = 'cda';
     881                                $thisfile_audio['lossless']     = true;
     882                                unset($info['mime_type']);
     883
     884                                $info['avdataoffset'] = 44;
     885
     886                                if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) {
     887                                        // shortcut
     888                                        $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0];
     889
     890                                        $thisfile_riff_CDDA_fmt_0['unknown1']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  0, 2));
     891                                        $thisfile_riff_CDDA_fmt_0['track_num']          = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  2, 2));
     892                                        $thisfile_riff_CDDA_fmt_0['disc_id']            = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  4, 4));
     893                                        $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  8, 4));
     894                                        $thisfile_riff_CDDA_fmt_0['playtime_frames']    = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4));
     895                                        $thisfile_riff_CDDA_fmt_0['unknown6']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4));
     896                                        $thisfile_riff_CDDA_fmt_0['unknown7']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4));
     897
     898                                        $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75;
     899                                        $thisfile_riff_CDDA_fmt_0['playtime_seconds']     = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75;
     900                                        $info['comments']['track']                = $thisfile_riff_CDDA_fmt_0['track_num'];
     901                                        $info['playtime_seconds']                 = $thisfile_riff_CDDA_fmt_0['playtime_seconds'];
     902
     903                                        // hardcoded data for CD-audio
     904                                        $thisfile_audio['sample_rate']     = 44100;
     905                                        $thisfile_audio['channels']        = 2;
     906                                        $thisfile_audio['bits_per_sample'] = 16;
     907                                        $thisfile_audio['bitrate']         = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample'];
     908                                        $thisfile_audio['bitrate_mode']    = 'cbr';
     909                                }
     910                                break;
     911
     912
     913                        case 'AIFF':
     914                        case 'AIFC':
     915                                $thisfile_audio['bitrate_mode'] = 'cbr';
     916                                $thisfile_audio_dataformat      = 'aiff';
     917                                $thisfile_audio['lossless']     = true;
     918                                $info['mime_type']      = 'audio/x-aiff';
     919
     920                                if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) {
     921                                        $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8;
     922                                        $info['avdataend']    = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size'];
     923                                        if ($info['avdataend'] > $info['filesize']) {
     924                                                if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) {
     925                                                        // structures rounded to 2-byte boundary, but dumb encoders
     926                                                        // forget to pad end of file to make this actually work
     927                                                } else {
     928                                                        $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found';
     929                                                }
     930                                                $info['avdataend'] = $info['filesize'];
     931                                        }
     932                                }
     933
     934                                if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) {
     935
     936                                        // shortcut
     937                                        $thisfile_riff_RIFFsubtype_COMM_0_data = &$thisfile_riff[$RIFFsubtype]['COMM'][0]['data'];
     938
     939                                        $thisfile_riff_audio['channels']         =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  0,  2), true);
     940                                        $thisfile_riff_audio['total_samples']    =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  2,  4), false);
     941                                        $thisfile_riff_audio['bits_per_sample']  =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  6,  2), true);
     942                                        $thisfile_riff_audio['sample_rate']      = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  8, 10));
     943
     944                                        if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) {
     945                                                $thisfile_riff_audio['codec_fourcc'] =                                   substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18,  4);
     946                                                $CodecNameSize                       =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22,  1), false);
     947                                                $thisfile_riff_audio['codec_name']   =                                   substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23,  $CodecNameSize);
     948                                                switch ($thisfile_riff_audio['codec_name']) {
     949                                                        case 'NONE':
     950                                                                $thisfile_audio['codec']    = 'Pulse Code Modulation (PCM)';
     951                                                                $thisfile_audio['lossless'] = true;
     952                                                                break;
     953
     954                                                        case '':
     955                                                                switch ($thisfile_riff_audio['codec_fourcc']) {
     956                                                                        // http://developer.apple.com/qa/snd/snd07.html
     957                                                                        case 'sowt':
     958                                                                                $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM';
     959                                                                                $thisfile_audio['lossless'] = true;
     960                                                                                break;
     961
     962                                                                        case 'twos':
     963                                                                                $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM';
     964                                                                                $thisfile_audio['lossless'] = true;
     965                                                                                break;
     966
     967                                                                        default:
     968                                                                                break;
     969                                                                }
     970                                                                break;
     971
     972                                                        default:
     973                                                                $thisfile_audio['codec']    = $thisfile_riff_audio['codec_name'];
     974                                                                $thisfile_audio['lossless'] = false;
     975                                                                break;
     976                                                }
     977                                        }
     978
     979                                        $thisfile_audio['channels']        = $thisfile_riff_audio['channels'];
     980                                        if ($thisfile_riff_audio['bits_per_sample'] > 0) {
     981                                                $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample'];
     982                                        }
     983                                        $thisfile_audio['sample_rate']     = $thisfile_riff_audio['sample_rate'];
     984                                        if ($thisfile_audio['sample_rate'] == 0) {
     985                                                $info['error'][] = 'Corrupted AIFF file: sample_rate == zero';
     986                                                return false;
     987                                        }
     988                                        $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate'];
     989                                }
     990
     991                                if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) {
     992                                        $offset = 0;
     993                                        $CommentCount                                   = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false);
     994                                        $offset += 2;
     995                                        for ($i = 0; $i < $CommentCount; $i++) {
     996                                                $info['comments_raw'][$i]['timestamp']      = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false);
     997                                                $offset += 4;
     998                                                $info['comments_raw'][$i]['marker_id']      = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true);
     999                                                $offset += 2;
     1000                                                $CommentLength                              = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false);
     1001                                                $offset += 2;
     1002                                                $info['comments_raw'][$i]['comment']        =                           substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength);
     1003                                                $offset += $CommentLength;
     1004
     1005                                                $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']);
     1006                                                $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment'];
     1007                                        }
     1008                                }
     1009
     1010                                $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment');
     1011                                foreach ($CommentsChunkNames as $key => $value) {
     1012                                        if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) {
     1013                                                $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data'];
     1014                                        }
     1015                                }
     1016/*
     1017                                if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) {
     1018                                        getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
     1019                                        $getid3_temp = new getID3();
     1020                                        $getid3_temp->openfile($this->getid3->filename);
     1021                                        $getid3_id3v2 = new getid3_id3v2($getid3_temp);
     1022                                        $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8;
     1023                                        if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
     1024                                                $info['id3v2'] = $getid3_temp->info['id3v2'];
     1025                                        }
     1026                                        unset($getid3_temp, $getid3_id3v2);
     1027                                }
     1028*/
     1029                                break;
     1030
     1031                        case '8SVX':
     1032                                $thisfile_audio['bitrate_mode']    = 'cbr';
     1033                                $thisfile_audio_dataformat         = '8svx';
     1034                                $thisfile_audio['bits_per_sample'] = 8;
     1035                                $thisfile_audio['channels']        = 1; // overridden below, if need be
     1036                                $info['mime_type']                = 'audio/x-aiff';
     1037
     1038                                if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) {
     1039                                        $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8;
     1040                                        $info['avdataend']    = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size'];
     1041                                        if ($info['avdataend'] > $info['filesize']) {
     1042                                                $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found';
     1043                                        }
     1044                                }
     1045
     1046                                if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) {
     1047                                        // shortcut
     1048                                        $thisfile_riff_RIFFsubtype_VHDR_0 = &$thisfile_riff[$RIFFsubtype]['VHDR'][0];
     1049
     1050                                        $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples']  =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  0, 4));
     1051                                        $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples']   =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  4, 4));
     1052                                        $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  8, 4));
     1053                                        $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']     =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2));
     1054                                        $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave']          =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1));
     1055                                        $thisfile_riff_RIFFsubtype_VHDR_0['sCompression']      =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1));
     1056                                        $thisfile_riff_RIFFsubtype_VHDR_0['Volume']            = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4));
     1057
     1058                                        $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'];
     1059
     1060                                        switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) {
     1061                                                case 0:
     1062                                                        $thisfile_audio['codec']    = 'Pulse Code Modulation (PCM)';
     1063                                                        $thisfile_audio['lossless'] = true;
     1064                                                        $ActualBitsPerSample        = 8;
     1065                                                        break;
     1066
     1067                                                case 1:
     1068                                                        $thisfile_audio['codec']    = 'Fibonacci-delta encoding';
     1069                                                        $thisfile_audio['lossless'] = false;
     1070                                                        $ActualBitsPerSample        = 4;
     1071                                                        break;
     1072
     1073                                                default:
     1074                                                        $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"';
     1075                                                        break;
     1076                                        }
     1077                                }
     1078
     1079                                if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) {
     1080                                        $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4));
     1081                                        switch ($ChannelsIndex) {
     1082                                                case 6: // Stereo
     1083                                                        $thisfile_audio['channels'] = 2;
     1084                                                        break;
     1085
     1086                                                case 2: // Left channel only
     1087                                                case 4: // Right channel only
     1088                                                        $thisfile_audio['channels'] = 1;
     1089                                                        break;
     1090
     1091                                                default:
     1092                                                        $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"';
     1093                                                        break;
     1094                                        }
     1095
     1096                                }
     1097
     1098                                $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment');
     1099                                foreach ($CommentsChunkNames as $key => $value) {
     1100                                        if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) {
     1101                                                $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data'];
     1102                                        }
     1103                                }
     1104
     1105                                $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels'];
     1106                                if (!empty($thisfile_audio['bitrate'])) {
     1107                                        $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8);
     1108                                }
     1109                                break;
     1110
     1111
     1112                        case 'CDXA':
     1113                                $info['mime_type'] = 'video/mpeg';
     1114                                if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) {
     1115                                        if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) {
     1116                                                $getid3_temp = new getID3();
     1117                                                $getid3_temp->openfile($this->getid3->filename);
     1118                                                $getid3_mpeg = new getid3_mpeg($getid3_temp);
     1119                                                $getid3_mpeg->Analyze();
     1120                                                if (empty($getid3_temp->info['error'])) {
     1121                                                        $info['audio']   = $getid3_temp->info['audio'];
     1122                                                        $info['video']   = $getid3_temp->info['video'];
     1123                                                        $info['mpeg']    = $getid3_temp->info['mpeg'];
     1124                                                        $info['warning'] = $getid3_temp->info['warning'];
     1125                                                }
     1126                                                unset($getid3_temp, $getid3_mpeg);
     1127                                        }
     1128                                }
     1129                                break;
     1130
     1131
     1132                        default:
     1133                                $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead';
     1134                                unset($info['fileformat']);
     1135                                break;
     1136                }
     1137
     1138                switch ($RIFFsubtype) {
     1139                        case 'WAVE':
     1140                        case 'AIFF':
     1141                        case 'AIFC':
     1142                                $ID3v2_key_good = 'id3 ';
     1143                                $ID3v2_keys_bad = array('ID3 ', 'tag ');
     1144                                foreach ($ID3v2_keys_bad as $ID3v2_key_bad) {
     1145                                        if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) {
     1146                                                $thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad];
     1147                                                $info['warning'][] = 'mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"';
     1148                                        }
     1149                                }
     1150
     1151                                if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) {
     1152                                        getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
     1153                                        $getid3_temp = new getID3();
     1154                                        $getid3_temp->openfile($this->getid3->filename);
     1155                                        $getid3_id3v2 = new getid3_id3v2($getid3_temp);
     1156                                        $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8;
     1157                                        if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
     1158                                                $info['id3v2'] = $getid3_temp->info['id3v2'];
     1159                                        }
     1160                                        unset($getid3_temp, $getid3_id3v2);
     1161                                }
     1162                                break;
     1163                }
     1164
     1165                if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) {
     1166                        $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4));
     1167                }
     1168                if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) {
     1169                        self::parseComments($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']);
     1170                }
     1171                if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) {
     1172                        self::parseComments($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']);
     1173                }
     1174
     1175                if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) {
     1176                        $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version'];
     1177                }
     1178
     1179                if (!isset($info['playtime_seconds'])) {
     1180                        $info['playtime_seconds'] = 0;
     1181                }
     1182                if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) {
     1183                        // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie
     1184                        $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000);
     1185                } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) {
     1186                        $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000);
     1187                }
     1188
     1189                if ($info['playtime_seconds'] > 0) {
     1190                        if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) {
     1191
     1192                                if (!isset($info['bitrate'])) {
     1193                                        $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
     1194                                }
     1195
     1196                        } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) {
     1197
     1198                                if (!isset($thisfile_audio['bitrate'])) {
     1199                                        $thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
     1200                                }
     1201
     1202                        } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) {
     1203
     1204                                if (!isset($thisfile_video['bitrate'])) {
     1205                                        $thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
     1206                                }
     1207
     1208                        }
     1209                }
     1210
     1211
     1212                if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) {
     1213
     1214                        $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
     1215                        $thisfile_audio['bitrate'] = 0;
     1216                        $thisfile_video['bitrate'] = $info['bitrate'];
     1217                        foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) {
     1218                                $thisfile_video['bitrate'] -= $audioinfoarray['bitrate'];
     1219                                $thisfile_audio['bitrate'] += $audioinfoarray['bitrate'];
     1220                        }
     1221                        if ($thisfile_video['bitrate'] <= 0) {
     1222                                unset($thisfile_video['bitrate']);
     1223                        }
     1224                        if ($thisfile_audio['bitrate'] <= 0) {
     1225                                unset($thisfile_audio['bitrate']);
     1226                        }
     1227                }
     1228
     1229                if (isset($info['mpeg']['audio'])) {
     1230                        $thisfile_audio_dataformat      = 'mp'.$info['mpeg']['audio']['layer'];
     1231                        $thisfile_audio['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
     1232                        $thisfile_audio['channels']     = $info['mpeg']['audio']['channels'];
     1233                        $thisfile_audio['bitrate']      = $info['mpeg']['audio']['bitrate'];
     1234                        $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
     1235                        if (!empty($info['mpeg']['audio']['codec'])) {
     1236                                $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec'];
     1237                        }
     1238                        if (!empty($thisfile_audio['streams'])) {
     1239                                foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) {
     1240                                        if ($streamdata['dataformat'] == $thisfile_audio_dataformat) {
     1241                                                $thisfile_audio['streams'][$streamnumber]['sample_rate']  = $thisfile_audio['sample_rate'];
     1242                                                $thisfile_audio['streams'][$streamnumber]['channels']     = $thisfile_audio['channels'];
     1243                                                $thisfile_audio['streams'][$streamnumber]['bitrate']      = $thisfile_audio['bitrate'];
     1244                                                $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
     1245                                                $thisfile_audio['streams'][$streamnumber]['codec']        = $thisfile_audio['codec'];
     1246                                        }
     1247                                }
     1248                        }
     1249                        $getid3_mp3 = new getid3_mp3($this->getid3);
     1250                        $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions();
     1251                        unset($getid3_mp3);
     1252                }
     1253
     1254
     1255                if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && ($thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0)) {
     1256                        switch ($thisfile_audio_dataformat) {
     1257                                case 'ac3':
     1258                                        // ignore bits_per_sample
     1259                                        break;
     1260
     1261                                default:
     1262                                        $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample'];
     1263                                        break;
     1264                        }
     1265                }
     1266
     1267
     1268                if (empty($thisfile_riff_raw)) {
     1269                        unset($thisfile_riff['raw']);
     1270                }
     1271                if (empty($thisfile_riff_audio)) {
     1272                        unset($thisfile_riff['audio']);
     1273                }
     1274                if (empty($thisfile_riff_video)) {
     1275                        unset($thisfile_riff['video']);
     1276                }
     1277
     1278                return true;
     1279        }
     1280
     1281        public function ParseRIFF($startoffset, $maxoffset) {
     1282                $info = &$this->getid3->info;
     1283
     1284                $RIFFchunk = false;
     1285                $FoundAllChunksWeNeed = false;
     1286
     1287                try {
     1288                        $this->fseek($startoffset);
     1289                        $maxoffset = min($maxoffset, $info['avdataend']);
     1290                        while ($this->ftell() < $maxoffset) {
     1291                                $chunknamesize = $this->fread(8);
     1292                                //$chunkname =                          substr($chunknamesize, 0, 4);
     1293                                $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4));  // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult
     1294                                $chunksize =  $this->EitherEndian2Int(substr($chunknamesize, 4, 4));
     1295                                //if (strlen(trim($chunkname, "\x00")) < 4) {
     1296                                if (strlen($chunkname) < 4) {
     1297                                        $this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.');
     1298                                        break;
     1299                                }
     1300                                if (($chunksize == 0) && ($chunkname != 'JUNK')) {
     1301                                        $this->warning('Chunk ('.$chunkname.') size at offset '.($this->ftell() - 4).' is zero. Aborting RIFF parsing.');
     1302                                        break;
     1303                                }
     1304                                if (($chunksize % 2) != 0) {
     1305                                        // all structures are packed on word boundaries
     1306                                        $chunksize++;
     1307                                }
     1308
     1309                                switch ($chunkname) {
     1310                                        case 'LIST':
     1311                                                $listname = $this->fread(4);
     1312                                                if (preg_match('#^(movi|rec )$#i', $listname)) {
     1313                                                        $RIFFchunk[$listname]['offset'] = $this->ftell() - 4;
     1314                                                        $RIFFchunk[$listname]['size']   = $chunksize;
     1315
     1316                                                        if (!$FoundAllChunksWeNeed) {
     1317                                                                $WhereWeWere      = $this->ftell();
     1318                                                                $AudioChunkHeader = $this->fread(12);
     1319                                                                $AudioChunkStreamNum  =                              substr($AudioChunkHeader, 0, 2);
     1320                                                                $AudioChunkStreamType =                              substr($AudioChunkHeader, 2, 2);
     1321                                                                $AudioChunkSize       = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4));
     1322
     1323                                                                if ($AudioChunkStreamType == 'wb') {
     1324                                                                        $FirstFourBytes = substr($AudioChunkHeader, 8, 4);
     1325                                                                        if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) {
     1326                                                                                // MP3
     1327                                                                                if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) {
     1328                                                                                        $getid3_temp = new getID3();
     1329                                                                                        $getid3_temp->openfile($this->getid3->filename);
     1330                                                                                        $getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
     1331                                                                                        $getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
     1332                                                                                        $getid3_mp3 = new getid3_mp3($getid3_temp);
     1333                                                                                        $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
     1334                                                                                        if (isset($getid3_temp->info['mpeg']['audio'])) {
     1335                                                                                                $info['mpeg']['audio']         = $getid3_temp->info['mpeg']['audio'];
     1336                                                                                                $info['audio']                 = $getid3_temp->info['audio'];
     1337                                                                                                $info['audio']['dataformat']   = 'mp'.$info['mpeg']['audio']['layer'];
     1338                                                                                                $info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
     1339                                                                                                $info['audio']['channels']     = $info['mpeg']['audio']['channels'];
     1340                                                                                                $info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
     1341                                                                                                $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
     1342                                                                                                //$info['bitrate']               = $info['audio']['bitrate'];
     1343                                                                                        }
     1344                                                                                        unset($getid3_temp, $getid3_mp3);
     1345                                                                                }
     1346
     1347                                                                        } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) {
     1348
     1349                                                                                // AC3
     1350                                                                                $getid3_temp = new getID3();
     1351                                                                                $getid3_temp->openfile($this->getid3->filename);
     1352                                                                                $getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
     1353                                                                                $getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
     1354                                                                                $getid3_ac3 = new getid3_ac3($getid3_temp);
     1355                                                                                $getid3_ac3->Analyze();
     1356                                                                                if (empty($getid3_temp->info['error'])) {
     1357                                                                                        $info['audio']   = $getid3_temp->info['audio'];
     1358                                                                                        $info['ac3']     = $getid3_temp->info['ac3'];
     1359                                                                                        if (!empty($getid3_temp->info['warning'])) {
     1360                                                                                                foreach ($getid3_temp->info['warning'] as $key => $value) {
     1361                                                                                                        $info['warning'][] = $value;
     1362                                                                                                }
     1363                                                                                        }
     1364                                                                                }
     1365                                                                                unset($getid3_temp, $getid3_ac3);
     1366                                                                        }
     1367                                                                }
     1368                                                                $FoundAllChunksWeNeed = true;
     1369                                                                $this->fseek($WhereWeWere);
     1370                                                        }
     1371                                                        $this->fseek($chunksize - 4, SEEK_CUR);
     1372
     1373                                                } else {
     1374
     1375                                                        if (!isset($RIFFchunk[$listname])) {
     1376                                                                $RIFFchunk[$listname] = array();
     1377                                                        }
     1378                                                        $LISTchunkParent    = $listname;
     1379                                                        $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize;
     1380                                                        if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) {
     1381                                                                $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk);
     1382                                                        }
     1383
     1384                                                }
     1385                                                break;
     1386
     1387                                        default:
     1388                                                if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) {
     1389                                                        $this->fseek($chunksize, SEEK_CUR);
     1390                                                        break;
     1391                                                }
     1392                                                $thisindex = 0;
     1393                                                if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) {
     1394                                                        $thisindex = count($RIFFchunk[$chunkname]);
     1395                                                }
     1396                                                $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8;
     1397                                                $RIFFchunk[$chunkname][$thisindex]['size']   = $chunksize;
     1398                                                switch ($chunkname) {
     1399                                                        case 'data':
     1400                                                                $info['avdataoffset'] = $this->ftell();
     1401                                                                $info['avdataend']    = $info['avdataoffset'] + $chunksize;
     1402
     1403                                                                $testData = $this->fread(36);
     1404                                                                if ($testData === '') {
     1405                                                                        break;
     1406                                                                }
     1407                                                                if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) {
     1408
     1409                                                                        // Probably is MP3 data
     1410                                                                        if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) {
     1411                                                                                $getid3_temp = new getID3();
     1412                                                                                $getid3_temp->openfile($this->getid3->filename);
     1413                                                                                $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
     1414                                                                                $getid3_temp->info['avdataend']    = $info['avdataend'];
     1415                                                                                $getid3_mp3 = new getid3_mp3($getid3_temp);
     1416                                                                                $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false);
     1417                                                                                if (empty($getid3_temp->info['error'])) {
     1418                                                                                        $info['audio'] = $getid3_temp->info['audio'];
     1419                                                                                        $info['mpeg']  = $getid3_temp->info['mpeg'];
     1420                                                                                }
     1421                                                                                unset($getid3_temp, $getid3_mp3);
     1422                                                                        }
     1423
     1424                                                                } elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) {
     1425
     1426                                                                        // This is probably AC-3 data
     1427                                                                        $getid3_temp = new getID3();
     1428                                                                        if ($isRegularAC3) {
     1429                                                                                $getid3_temp->openfile($this->getid3->filename);
     1430                                                                                $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
     1431                                                                                $getid3_temp->info['avdataend']    = $info['avdataend'];
     1432                                                                        }
     1433                                                                        $getid3_ac3 = new getid3_ac3($getid3_temp);
     1434                                                                        if ($isRegularAC3) {
     1435                                                                                $getid3_ac3->Analyze();
     1436                                                                        } else {
     1437                                                                                // Dolby Digital WAV
     1438                                                                                // AC-3 content, but not encoded in same format as normal AC-3 file
     1439                                                                                // For one thing, byte order is swapped
     1440                                                                                $ac3_data = '';
     1441                                                                                for ($i = 0; $i < 28; $i += 2) {
     1442                                                                                        $ac3_data .= substr($testData, 8 + $i + 1, 1);
     1443                                                                                        $ac3_data .= substr($testData, 8 + $i + 0, 1);
     1444                                                                                }
     1445                                                                                $getid3_ac3->AnalyzeString($ac3_data);
     1446                                                                        }
     1447
     1448                                                                        if (empty($getid3_temp->info['error'])) {
     1449                                                                                $info['audio'] = $getid3_temp->info['audio'];
     1450                                                                                $info['ac3']   = $getid3_temp->info['ac3'];
     1451                                                                                if (!empty($getid3_temp->info['warning'])) {
     1452                                                                                        foreach ($getid3_temp->info['warning'] as $newerror) {
     1453                                                                                                $this->warning('getid3_ac3() says: ['.$newerror.']');
     1454                                                                                        }
     1455                                                                                }
     1456                                                                        }
     1457                                                                        unset($getid3_temp, $getid3_ac3);
     1458
     1459                                                                } elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) {
     1460
     1461                                                                        // This is probably DTS data
     1462                                                                        $getid3_temp = new getID3();
     1463                                                                        $getid3_temp->openfile($this->getid3->filename);
     1464                                                                        $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
     1465                                                                        $getid3_dts = new getid3_dts($getid3_temp);
     1466                                                                        $getid3_dts->Analyze();
     1467                                                                        if (empty($getid3_temp->info['error'])) {
     1468                                                                                $info['audio']            = $getid3_temp->info['audio'];
     1469                                                                                $info['dts']              = $getid3_temp->info['dts'];
     1470                                                                                $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing
     1471                                                                                if (!empty($getid3_temp->info['warning'])) {
     1472                                                                                        foreach ($getid3_temp->info['warning'] as $newerror) {
     1473                                                                                                $this->warning('getid3_dts() says: ['.$newerror.']');
     1474                                                                                        }
     1475                                                                                }
     1476                                                                        }
     1477
     1478                                                                        unset($getid3_temp, $getid3_dts);
     1479
     1480                                                                } elseif (substr($testData, 0, 4) == 'wvpk') {
     1481
     1482                                                                        // This is WavPack data
     1483                                                                        $info['wavpack']['offset'] = $info['avdataoffset'];
     1484                                                                        $info['wavpack']['size']   = getid3_lib::LittleEndian2Int(substr($testData, 4, 4));
     1485                                                                        $this->parseWavPackHeader(substr($testData, 8, 28));
     1486
     1487                                                                } else {
     1488                                                                        // This is some other kind of data (quite possibly just PCM)
     1489                                                                        // do nothing special, just skip it
     1490                                                                }
     1491                                                                $nextoffset = $info['avdataend'];
     1492                                                                $this->fseek($nextoffset);
     1493                                                                break;
     1494
     1495                                                        case 'iXML':
     1496                                                        case 'bext':
     1497                                                        case 'cart':
     1498                                                        case 'fmt ':
     1499                                                        case 'strh':
     1500                                                        case 'strf':
     1501                                                        case 'indx':
     1502                                                        case 'MEXT':
     1503                                                        case 'DISP':
     1504                                                                // always read data in
     1505                                                        case 'JUNK':
     1506                                                                // should be: never read data in
     1507                                                                // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc)
     1508                                                                if ($chunksize < 1048576) {
     1509                                                                        if ($chunksize > 0) {
     1510                                                                                $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize);
     1511                                                                                if ($chunkname == 'JUNK') {
     1512                                                                                        if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) {
     1513                                                                                                // only keep text characters [chr(32)-chr(127)]
     1514                                                                                                $info['riff']['comments']['junk'][] = trim($matches[1]);
     1515                                                                                        }
     1516                                                                                        // but if nothing there, ignore
     1517                                                                                        // remove the key in either case
     1518                                                                                        unset($RIFFchunk[$chunkname][$thisindex]['data']);
     1519                                                                                }
     1520                                                                        }
     1521                                                                } else {
     1522                                                                        $this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data');
     1523                                                                        $this->fseek($chunksize, SEEK_CUR);
     1524                                                                }
     1525                                                                break;
     1526
     1527                                                        //case 'IDVX':
     1528                                                        //      $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize));
     1529                                                        //      break;
     1530
     1531                                                        default:
     1532                                                                if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) {
     1533                                                                        $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset'];
     1534                                                                        $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size']   = $RIFFchunk[$chunkname][$thisindex]['size'];
     1535                                                                        unset($RIFFchunk[$chunkname][$thisindex]['offset']);
     1536                                                                        unset($RIFFchunk[$chunkname][$thisindex]['size']);
     1537                                                                        if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) {
     1538                                                                                unset($RIFFchunk[$chunkname][$thisindex]);
     1539                                                                        }
     1540                                                                        if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) {
     1541                                                                                unset($RIFFchunk[$chunkname]);
     1542                                                                        }
     1543                                                                        $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize);
     1544                                                                } elseif ($chunksize < 2048) {
     1545                                                                        // only read data in if smaller than 2kB
     1546                                                                        $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize);
     1547                                                                } else {
     1548                                                                        $this->fseek($chunksize, SEEK_CUR);
     1549                                                                }
     1550                                                                break;
     1551                                                }
     1552                                                break;
     1553                                }
     1554                        }
     1555
     1556                } catch (getid3_exception $e) {
     1557                        if ($e->getCode() == 10) {
     1558                                $this->warning('RIFF parser: '.$e->getMessage());
     1559                        } else {
     1560                                throw $e;
     1561                        }
     1562                }
     1563
     1564                return $RIFFchunk;
     1565        }
     1566
     1567        public function ParseRIFFdata(&$RIFFdata) {
     1568                $info = &$this->getid3->info;
     1569                if ($RIFFdata) {
     1570                        $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3');
     1571                        $fp_temp  = fopen($tempfile, 'wb');
     1572                        $RIFFdataLength = strlen($RIFFdata);
     1573                        $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4);
     1574                        for ($i = 0; $i < 4; $i++) {
     1575                                $RIFFdata[($i + 4)] = $NewLengthString[$i];
     1576                        }
     1577                        fwrite($fp_temp, $RIFFdata);
     1578                        fclose($fp_temp);
     1579
     1580                        $getid3_temp = new getID3();
     1581                        $getid3_temp->openfile($tempfile);
     1582                        $getid3_temp->info['filesize']     = $RIFFdataLength;
     1583                        $getid3_temp->info['filenamepath'] = $info['filenamepath'];
     1584                        $getid3_temp->info['tags']         = $info['tags'];
     1585                        $getid3_temp->info['warning']      = $info['warning'];
     1586                        $getid3_temp->info['error']        = $info['error'];
     1587                        $getid3_temp->info['comments']     = $info['comments'];
     1588                        $getid3_temp->info['audio']        = (isset($info['audio']) ? $info['audio'] : array());
     1589                        $getid3_temp->info['video']        = (isset($info['video']) ? $info['video'] : array());
     1590                        $getid3_riff = new getid3_riff($getid3_temp);
     1591                        $getid3_riff->Analyze();
     1592
     1593                        $info['riff']     = $getid3_temp->info['riff'];
     1594                        $info['warning']  = $getid3_temp->info['warning'];
     1595                        $info['error']    = $getid3_temp->info['error'];
     1596                        $info['tags']     = $getid3_temp->info['tags'];
     1597                        $info['comments'] = $getid3_temp->info['comments'];
     1598                        unset($getid3_riff, $getid3_temp);
     1599                        unlink($tempfile);
     1600                }
     1601                return false;
     1602        }
     1603
     1604        public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) {
     1605                $RIFFinfoKeyLookup = array(
     1606                        'IARL'=>'archivallocation',
     1607                        'IART'=>'artist',
     1608                        'ICDS'=>'costumedesigner',
     1609                        'ICMS'=>'commissionedby',
     1610                        'ICMT'=>'comment',
     1611                        'ICNT'=>'country',
     1612                        'ICOP'=>'copyright',
     1613                        'ICRD'=>'creationdate',
     1614                        'IDIM'=>'dimensions',
     1615                        'IDIT'=>'digitizationdate',
     1616                        'IDPI'=>'resolution',
     1617                        'IDST'=>'distributor',
     1618                        'IEDT'=>'editor',
     1619                        'IENG'=>'engineers',
     1620                        'IFRM'=>'accountofparts',
     1621                        'IGNR'=>'genre',
     1622                        'IKEY'=>'keywords',
     1623                        'ILGT'=>'lightness',
     1624                        'ILNG'=>'language',
     1625                        'IMED'=>'orignalmedium',
     1626                        'IMUS'=>'composer',
     1627                        'INAM'=>'title',
     1628                        'IPDS'=>'productiondesigner',
     1629                        'IPLT'=>'palette',
     1630                        'IPRD'=>'product',
     1631                        'IPRO'=>'producer',
     1632                        'IPRT'=>'part',
     1633                        'IRTD'=>'rating',
     1634                        'ISBJ'=>'subject',
     1635                        'ISFT'=>'software',
     1636                        'ISGN'=>'secondarygenre',
     1637                        'ISHP'=>'sharpness',
     1638                        'ISRC'=>'sourcesupplier',
     1639                        'ISRF'=>'digitizationsource',
     1640                        'ISTD'=>'productionstudio',
     1641                        'ISTR'=>'starring',
     1642                        'ITCH'=>'encoded_by',
     1643                        'IWEB'=>'url',
     1644                        'IWRI'=>'writer',
     1645                        '____'=>'comment',
     1646                );
     1647                foreach ($RIFFinfoKeyLookup as $key => $value) {
     1648                        if (isset($RIFFinfoArray[$key])) {
     1649                                foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) {
     1650                                        if (trim($commentdata['data']) != '') {
     1651                                                if (isset($CommentsTargetArray[$value])) {
     1652                                                        $CommentsTargetArray[$value][] =     trim($commentdata['data']);
     1653                                                } else {
     1654                                                        $CommentsTargetArray[$value] = array(trim($commentdata['data']));
     1655                                                }
     1656                                        }
     1657                                }
     1658                        }
     1659                }
     1660                return true;
     1661        }
     1662
     1663        public static function parseWAVEFORMATex($WaveFormatExData) {
     1664                // shortcut
     1665                $WaveFormatEx['raw'] = array();
     1666                $WaveFormatEx_raw    = &$WaveFormatEx['raw'];
     1667
     1668                $WaveFormatEx_raw['wFormatTag']      = substr($WaveFormatExData,  0, 2);
     1669                $WaveFormatEx_raw['nChannels']       = substr($WaveFormatExData,  2, 2);
     1670                $WaveFormatEx_raw['nSamplesPerSec']  = substr($WaveFormatExData,  4, 4);
     1671                $WaveFormatEx_raw['nAvgBytesPerSec'] = substr($WaveFormatExData,  8, 4);
     1672                $WaveFormatEx_raw['nBlockAlign']     = substr($WaveFormatExData, 12, 2);
     1673                $WaveFormatEx_raw['wBitsPerSample']  = substr($WaveFormatExData, 14, 2);
     1674                if (strlen($WaveFormatExData) > 16) {
     1675                        $WaveFormatEx_raw['cbSize']      = substr($WaveFormatExData, 16, 2);
     1676                }
     1677                $WaveFormatEx_raw = array_map('getid3_lib::LittleEndian2Int', $WaveFormatEx_raw);
     1678
     1679                $WaveFormatEx['codec']           = self::wFormatTagLookup($WaveFormatEx_raw['wFormatTag']);
     1680                $WaveFormatEx['channels']        = $WaveFormatEx_raw['nChannels'];
     1681                $WaveFormatEx['sample_rate']     = $WaveFormatEx_raw['nSamplesPerSec'];
     1682                $WaveFormatEx['bitrate']         = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8;
     1683                $WaveFormatEx['bits_per_sample'] = $WaveFormatEx_raw['wBitsPerSample'];
     1684
     1685                return $WaveFormatEx;
     1686        }
     1687
     1688        public function parseWavPackHeader($WavPackChunkData) {
     1689                // typedef struct {
     1690                //     char ckID [4];
     1691                //     long ckSize;
     1692                //     short version;
     1693                //     short bits;                // added for version 2.00
     1694                //     short flags, shift;        // added for version 3.00
     1695                //     long total_samples, crc, crc2;
     1696                //     char extension [4], extra_bc, extras [3];
     1697                // } WavpackHeader;
     1698
     1699                // shortcut
     1700                $info = &$this->getid3->info;
     1701                $info['wavpack']  = array();
     1702                $thisfile_wavpack = &$info['wavpack'];
     1703
     1704                $thisfile_wavpack['version']           = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  0, 2));
     1705                if ($thisfile_wavpack['version'] >= 2) {
     1706                        $thisfile_wavpack['bits']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  2, 2));
     1707                }
     1708                if ($thisfile_wavpack['version'] >= 3) {
     1709                        $thisfile_wavpack['flags_raw']     = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  4, 2));
     1710                        $thisfile_wavpack['shift']         = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  6, 2));
     1711                        $thisfile_wavpack['total_samples'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  8, 4));
     1712                        $thisfile_wavpack['crc1']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 12, 4));
     1713                        $thisfile_wavpack['crc2']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 16, 4));
     1714                        $thisfile_wavpack['extension']     =                              substr($WavPackChunkData, 20, 4);
     1715                        $thisfile_wavpack['extra_bc']      = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 24, 1));
     1716                        for ($i = 0; $i <= 2; $i++) {
     1717                                $thisfile_wavpack['extras'][]  = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 25 + $i, 1));
     1718                        }
     1719
     1720                        // shortcut
     1721                        $thisfile_wavpack['flags'] = array();
     1722                        $thisfile_wavpack_flags = &$thisfile_wavpack['flags'];
     1723
     1724                        $thisfile_wavpack_flags['mono']                 = (bool) ($thisfile_wavpack['flags_raw'] & 0x000001);
     1725                        $thisfile_wavpack_flags['fast_mode']            = (bool) ($thisfile_wavpack['flags_raw'] & 0x000002);
     1726                        $thisfile_wavpack_flags['raw_mode']             = (bool) ($thisfile_wavpack['flags_raw'] & 0x000004);
     1727                        $thisfile_wavpack_flags['calc_noise']           = (bool) ($thisfile_wavpack['flags_raw'] & 0x000008);
     1728                        $thisfile_wavpack_flags['high_quality']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000010);
     1729                        $thisfile_wavpack_flags['3_byte_samples']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000020);
     1730                        $thisfile_wavpack_flags['over_20_bits']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000040);
     1731                        $thisfile_wavpack_flags['use_wvc']              = (bool) ($thisfile_wavpack['flags_raw'] & 0x000080);
     1732                        $thisfile_wavpack_flags['noiseshaping']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000100);
     1733                        $thisfile_wavpack_flags['very_fast_mode']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000200);
     1734                        $thisfile_wavpack_flags['new_high_quality']     = (bool) ($thisfile_wavpack['flags_raw'] & 0x000400);
     1735                        $thisfile_wavpack_flags['cancel_extreme']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000800);
     1736                        $thisfile_wavpack_flags['cross_decorrelation']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x001000);
     1737                        $thisfile_wavpack_flags['new_decorrelation']    = (bool) ($thisfile_wavpack['flags_raw'] & 0x002000);
     1738                        $thisfile_wavpack_flags['joint_stereo']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x004000);
     1739                        $thisfile_wavpack_flags['extra_decorrelation']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x008000);
     1740                        $thisfile_wavpack_flags['override_noiseshape']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x010000);
     1741                        $thisfile_wavpack_flags['override_jointstereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x020000);
     1742                        $thisfile_wavpack_flags['copy_source_filetime'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x040000);
     1743                        $thisfile_wavpack_flags['create_exe']           = (bool) ($thisfile_wavpack['flags_raw'] & 0x080000);
     1744                }
     1745
     1746                return true;
     1747        }
     1748
     1749        public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {
     1750
     1751                $parsed['biSize']          = substr($BITMAPINFOHEADER,  0, 4); // number of bytes required by the BITMAPINFOHEADER structure
     1752                $parsed['biWidth']         = substr($BITMAPINFOHEADER,  4, 4); // width of the bitmap in pixels
     1753                $parsed['biHeight']        = substr($BITMAPINFOHEADER,  8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner
     1754                $parsed['biPlanes']        = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1
     1755                $parsed['biBitCount']      = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels
     1756                $parsed['biSizeImage']     = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures)
     1757                $parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device
     1758                $parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device
     1759                $parsed['biClrUsed']       = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression
     1760                $parsed['biClrImportant']  = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important
     1761                $parsed = array_map('getid3_lib::'.($littleEndian ? 'Little' : 'Big').'Endian2Int', $parsed);
     1762
     1763                $parsed['fourcc']          = substr($BITMAPINFOHEADER, 16, 4);  // compression identifier
     1764
     1765                return $parsed;
     1766        }
     1767
     1768        public static function ParseDIVXTAG($DIVXTAG, $raw=false) {
     1769                // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/
     1770                // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip
     1771                // 'Byte Layout:                   '1111111111111111
     1772                // '32 for Movie - 1               '1111111111111111
     1773                // '28 for Author - 6              '6666666666666666
     1774                // '4  for year - 2                '6666666666662222
     1775                // '3  for genre - 3               '7777777777777777
     1776                // '48 for Comments - 7            '7777777777777777
     1777                // '1  for Rating - 4              '7777777777777777
     1778                // '5  for Future Additions - 0    '333400000DIVXTAG
     1779                // '128 bytes total
     1780
     1781                static $DIVXTAGgenre  = array(
     1782                         0 => 'Action',
     1783                         1 => 'Action/Adventure',
     1784                         2 => 'Adventure',
     1785                         3 => 'Adult',
     1786                         4 => 'Anime',
     1787                         5 => 'Cartoon',
     1788                         6 => 'Claymation',
     1789                         7 => 'Comedy',
     1790                         8 => 'Commercial',
     1791                         9 => 'Documentary',
     1792                        10 => 'Drama',
     1793                        11 => 'Home Video',
     1794                        12 => 'Horror',
     1795                        13 => 'Infomercial',
     1796                        14 => 'Interactive',
     1797                        15 => 'Mystery',
     1798                        16 => 'Music Video',
     1799                        17 => 'Other',
     1800                        18 => 'Religion',
     1801                        19 => 'Sci Fi',
     1802                        20 => 'Thriller',
     1803                        21 => 'Western',
     1804                ),
     1805                $DIVXTAGrating = array(
     1806                         0 => 'Unrated',
     1807                         1 => 'G',
     1808                         2 => 'PG',
     1809                         3 => 'PG-13',
     1810                         4 => 'R',
     1811                         5 => 'NC-17',
     1812                );
     1813
     1814                $parsed['title']     =        trim(substr($DIVXTAG,   0, 32));
     1815                $parsed['artist']    =        trim(substr($DIVXTAG,  32, 28));
     1816                $parsed['year']      = intval(trim(substr($DIVXTAG,  60,  4)));
     1817                $parsed['comment']   =        trim(substr($DIVXTAG,  64, 48));
     1818                $parsed['genre_id']  = intval(trim(substr($DIVXTAG, 112,  3)));
     1819                $parsed['rating_id'] =         ord(substr($DIVXTAG, 115,  1));
     1820                //$parsed['padding'] =             substr($DIVXTAG, 116,  5);  // 5-byte null
     1821                //$parsed['magic']   =             substr($DIVXTAG, 121,  7);  // "DIVXTAG"
     1822
     1823                $parsed['genre']  = (isset($DIVXTAGgenre[$parsed['genre_id']])   ? $DIVXTAGgenre[$parsed['genre_id']]   : $parsed['genre_id']);
     1824                $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']);
     1825
     1826                if (!$raw) {
     1827                        unset($parsed['genre_id'], $parsed['rating_id']);
     1828                        foreach ($parsed as $key => $value) {
     1829                                if (!$value === '') {
     1830                                        unset($parsed['key']);
     1831                                }
     1832                        }
     1833                }
     1834
     1835                foreach ($parsed as $tag => $value) {
     1836                        $parsed[$tag] = array($value);
     1837                }
     1838
     1839                return $parsed;
     1840        }
     1841
     1842        public static function waveSNDMtagLookup($tagshortname) {
     1843                $begin = __LINE__;
     1844
     1845                /** This is not a comment!
     1846
     1847                        ©kwd    keywords
     1848                        ©BPM    bpm
     1849                        ©trt    tracktitle
     1850                        ©des    description
     1851                        ©gen    category
     1852                        ©fin    featuredinstrument
     1853                        ©LID    longid
     1854                        ©bex    bwdescription
     1855                        ©pub    publisher
     1856                        ©cdt    cdtitle
     1857                        ©alb    library
     1858                        ©com    composer
     1859
     1860                */
     1861
     1862                return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm');
     1863        }
     1864
     1865        public static function wFormatTagLookup($wFormatTag) {
     1866
     1867                $begin = __LINE__;
     1868
     1869                /** This is not a comment!
     1870
     1871                        0x0000  Microsoft Unknown Wave Format
     1872                        0x0001  Pulse Code Modulation (PCM)
     1873                        0x0002  Microsoft ADPCM
     1874                        0x0003  IEEE Float
     1875                        0x0004  Compaq Computer VSELP
     1876                        0x0005  IBM CVSD
     1877                        0x0006  Microsoft A-Law
     1878                        0x0007  Microsoft mu-Law
     1879                        0x0008  Microsoft DTS
     1880                        0x0010  OKI ADPCM
     1881                        0x0011  Intel DVI/IMA ADPCM
     1882                        0x0012  Videologic MediaSpace ADPCM
     1883                        0x0013  Sierra Semiconductor ADPCM
     1884                        0x0014  Antex Electronics G.723 ADPCM
     1885                        0x0015  DSP Solutions DigiSTD
     1886                        0x0016  DSP Solutions DigiFIX
     1887                        0x0017  Dialogic OKI ADPCM
     1888                        0x0018  MediaVision ADPCM
     1889                        0x0019  Hewlett-Packard CU
     1890                        0x0020  Yamaha ADPCM
     1891                        0x0021  Speech Compression Sonarc
     1892                        0x0022  DSP Group TrueSpeech
     1893                        0x0023  Echo Speech EchoSC1
     1894                        0x0024  Audiofile AF36
     1895                        0x0025  Audio Processing Technology APTX
     1896                        0x0026  AudioFile AF10
     1897                        0x0027  Prosody 1612
     1898                        0x0028  LRC
     1899                        0x0030  Dolby AC2
     1900                        0x0031  Microsoft GSM 6.10
     1901                        0x0032  MSNAudio
     1902                        0x0033  Antex Electronics ADPCME
     1903                        0x0034  Control Resources VQLPC
     1904                        0x0035  DSP Solutions DigiREAL
     1905                        0x0036  DSP Solutions DigiADPCM
     1906                        0x0037  Control Resources CR10
     1907                        0x0038  Natural MicroSystems VBXADPCM
     1908                        0x0039  Crystal Semiconductor IMA ADPCM
     1909                        0x003A  EchoSC3
     1910                        0x003B  Rockwell ADPCM
     1911                        0x003C  Rockwell Digit LK
     1912                        0x003D  Xebec
     1913                        0x0040  Antex Electronics G.721 ADPCM
     1914                        0x0041  G.728 CELP
     1915                        0x0042  MSG723
     1916                        0x0050  MPEG Layer-2 or Layer-1
     1917                        0x0052  RT24
     1918                        0x0053  PAC
     1919                        0x0055  MPEG Layer-3
     1920                        0x0059  Lucent G.723
     1921                        0x0060  Cirrus
     1922                        0x0061  ESPCM
     1923                        0x0062  Voxware
     1924                        0x0063  Canopus Atrac
     1925                        0x0064  G.726 ADPCM
     1926                        0x0065  G.722 ADPCM
     1927                        0x0066  DSAT
     1928                        0x0067  DSAT Display
     1929                        0x0069  Voxware Byte Aligned
     1930                        0x0070  Voxware AC8
     1931                        0x0071  Voxware AC10
     1932                        0x0072  Voxware AC16
     1933                        0x0073  Voxware AC20
     1934                        0x0074  Voxware MetaVoice
     1935                        0x0075  Voxware MetaSound
     1936                        0x0076  Voxware RT29HW
     1937                        0x0077  Voxware VR12
     1938                        0x0078  Voxware VR18
     1939                        0x0079  Voxware TQ40
     1940                        0x0080  Softsound
     1941                        0x0081  Voxware TQ60
     1942                        0x0082  MSRT24
     1943                        0x0083  G.729A
     1944                        0x0084  MVI MV12
     1945                        0x0085  DF G.726
     1946                        0x0086  DF GSM610
     1947                        0x0088  ISIAudio
     1948                        0x0089  Onlive
     1949                        0x0091  SBC24
     1950                        0x0092  Dolby AC3 SPDIF
     1951                        0x0093  MediaSonic G.723
     1952                        0x0094  Aculab PLC    Prosody 8kbps
     1953                        0x0097  ZyXEL ADPCM
     1954                        0x0098  Philips LPCBB
     1955                        0x0099  Packed
     1956                        0x00FF  AAC
     1957                        0x0100  Rhetorex ADPCM
     1958                        0x0101  IBM mu-law
     1959                        0x0102  IBM A-law
     1960                        0x0103  IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM)
     1961                        0x0111  Vivo G.723
     1962                        0x0112  Vivo Siren
     1963                        0x0123  Digital G.723
     1964                        0x0125  Sanyo LD ADPCM
     1965                        0x0130  Sipro Lab Telecom ACELP NET
     1966                        0x0131  Sipro Lab Telecom ACELP 4800
     1967                        0x0132  Sipro Lab Telecom ACELP 8V3
     1968                        0x0133  Sipro Lab Telecom G.729
     1969                        0x0134  Sipro Lab Telecom G.729A
     1970                        0x0135  Sipro Lab Telecom Kelvin
     1971                        0x0140  Windows Media Video V8
     1972                        0x0150  Qualcomm PureVoice
     1973                        0x0151  Qualcomm HalfRate
     1974                        0x0155  Ring Zero Systems TUB GSM
     1975                        0x0160  Microsoft Audio 1
     1976                        0x0161  Windows Media Audio V7 / V8 / V9
     1977                        0x0162  Windows Media Audio Professional V9
     1978                        0x0163  Windows Media Audio Lossless V9
     1979                        0x0200  Creative Labs ADPCM
     1980                        0x0202  Creative Labs Fastspeech8
     1981                        0x0203  Creative Labs Fastspeech10
     1982                        0x0210  UHER Informatic GmbH ADPCM
     1983                        0x0220  Quarterdeck
     1984                        0x0230  I-link Worldwide VC
     1985                        0x0240  Aureal RAW Sport
     1986                        0x0250  Interactive Products HSX
     1987                        0x0251  Interactive Products RPELP
     1988                        0x0260  Consistent Software CS2
     1989                        0x0270  Sony SCX
     1990                        0x0300  Fujitsu FM Towns Snd
     1991                        0x0400  BTV Digital
     1992                        0x0401  Intel Music Coder
     1993                        0x0450  QDesign Music
     1994                        0x0680  VME VMPCM
     1995                        0x0681  AT&T Labs TPC
     1996                        0x08AE  ClearJump LiteWave
     1997                        0x1000  Olivetti GSM
     1998                        0x1001  Olivetti ADPCM
     1999                        0x1002  Olivetti CELP
     2000                        0x1003  Olivetti SBC
     2001                        0x1004  Olivetti OPR
     2002                        0x1100  Lernout & Hauspie Codec (0x1100)
     2003                        0x1101  Lernout & Hauspie CELP Codec (0x1101)
     2004                        0x1102  Lernout & Hauspie SBC Codec (0x1102)
     2005                        0x1103  Lernout & Hauspie SBC Codec (0x1103)
     2006                        0x1104  Lernout & Hauspie SBC Codec (0x1104)
     2007                        0x1400  Norris
     2008                        0x1401  AT&T ISIAudio
     2009                        0x1500  Soundspace Music Compression
     2010                        0x181C  VoxWare RT24 Speech
     2011                        0x1FC4  NCT Soft ALF2CD (www.nctsoft.com)
     2012                        0x2000  Dolby AC3
     2013                        0x2001  Dolby DTS
     2014                        0x2002  WAVE_FORMAT_14_4
     2015                        0x2003  WAVE_FORMAT_28_8
     2016                        0x2004  WAVE_FORMAT_COOK
     2017                        0x2005  WAVE_FORMAT_DNET
     2018                        0x674F  Ogg Vorbis 1
     2019                        0x6750  Ogg Vorbis 2
     2020                        0x6751  Ogg Vorbis 3
     2021                        0x676F  Ogg Vorbis 1+
     2022                        0x6770  Ogg Vorbis 2+
     2023                        0x6771  Ogg Vorbis 3+
     2024                        0x7A21  GSM-AMR (CBR, no SID)
     2025                        0x7A22  GSM-AMR (VBR, including SID)
     2026                        0xFFFE  WAVE_FORMAT_EXTENSIBLE
     2027                        0xFFFF  WAVE_FORMAT_DEVELOPMENT
     2028
     2029                */
     2030
     2031                return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag');
     2032        }
     2033
     2034        public static function fourccLookup($fourcc) {
     2035
     2036                $begin = __LINE__;
     2037
     2038                /** This is not a comment!
     2039
     2040                        swot    http://developer.apple.com/qa/snd/snd07.html
     2041                        ____    No Codec (____)
     2042                        _BIT    BI_BITFIELDS (Raw RGB)
     2043                        _JPG    JPEG compressed
     2044                        _PNG    PNG compressed W3C/ISO/IEC (RFC-2083)
     2045                        _RAW    Full Frames (Uncompressed)
     2046                        _RGB    Raw RGB Bitmap
     2047                        _RL4    RLE 4bpp RGB
     2048                        _RL8    RLE 8bpp RGB
     2049                        3IV1    3ivx MPEG-4 v1
     2050                        3IV2    3ivx MPEG-4 v2
     2051                        3IVX    3ivx MPEG-4
     2052                        AASC    Autodesk Animator
     2053                        ABYR    Kensington ?ABYR?
     2054                        AEMI    Array Microsystems VideoONE MPEG1-I Capture
     2055                        AFLC    Autodesk Animator FLC
     2056                        AFLI    Autodesk Animator FLI
     2057                        AMPG    Array Microsystems VideoONE MPEG
     2058                        ANIM    Intel RDX (ANIM)
     2059                        AP41    AngelPotion Definitive
     2060                        ASV1    Asus Video v1
     2061                        ASV2    Asus Video v2
     2062                        ASVX    Asus Video 2.0 (audio)
     2063                        AUR2    AuraVision Aura 2 Codec - YUV 4:2:2
     2064                        AURA    AuraVision Aura 1 Codec - YUV 4:1:1
     2065                        AVDJ    Independent JPEG Group\'s codec (AVDJ)
     2066                        AVRN    Independent JPEG Group\'s codec (AVRN)
     2067                        AYUV    4:4:4 YUV (AYUV)
     2068                        AZPR    Quicktime Apple Video (AZPR)
     2069                        BGR     Raw RGB32
     2070                        BLZ0    Blizzard DivX MPEG-4
     2071                        BTVC    Conexant Composite Video
     2072                        BINK    RAD Game Tools Bink Video
     2073                        BT20    Conexant Prosumer Video
     2074                        BTCV    Conexant Composite Video Codec
     2075                        BW10    Data Translation Broadway MPEG Capture
     2076                        CC12    Intel YUV12
     2077                        CDVC    Canopus DV
     2078                        CFCC    Digital Processing Systems DPS Perception
     2079                        CGDI    Microsoft Office 97 Camcorder Video
     2080                        CHAM    Winnov Caviara Champagne
     2081                        CJPG    Creative WebCam JPEG
     2082                        CLJR    Cirrus Logic YUV 4:1:1
     2083                        CMYK    Common Data Format in Printing (Colorgraph)
     2084                        CPLA    Weitek 4:2:0 YUV Planar
     2085                        CRAM    Microsoft Video 1 (CRAM)
     2086                        cvid    Radius Cinepak
     2087                        CVID    Radius Cinepak
     2088                        CWLT    Microsoft Color WLT DIB
     2089                        CYUV    Creative Labs YUV
     2090                        CYUY    ATI YUV
     2091                        D261    H.261
     2092                        D263    H.263
     2093                        DIB     Device Independent Bitmap
     2094                        DIV1    FFmpeg OpenDivX
     2095                        DIV2    Microsoft MPEG-4 v1/v2
     2096                        DIV3    DivX ;-) MPEG-4 v3.x Low-Motion
     2097                        DIV4    DivX ;-) MPEG-4 v3.x Fast-Motion
     2098                        DIV5    DivX MPEG-4 v5.x
     2099                        DIV6    DivX ;-) (MS MPEG-4 v3.x)
     2100                        DIVX    DivX MPEG-4 v4 (OpenDivX / Project Mayo)
     2101                        divx    DivX MPEG-4
     2102                        DMB1    Matrox Rainbow Runner hardware MJPEG
     2103                        DMB2    Paradigm MJPEG
     2104                        DSVD    ?DSVD?
     2105                        DUCK    Duck TrueMotion 1.0
     2106                        DPS0    DPS/Leitch Reality Motion JPEG
     2107                        DPSC    DPS/Leitch PAR Motion JPEG
     2108                        DV25    Matrox DVCPRO codec
     2109                        DV50    Matrox DVCPRO50 codec
     2110                        DVC     IEC 61834 and SMPTE 314M (DVC/DV Video)
     2111                        DVCP    IEC 61834 and SMPTE 314M (DVC/DV Video)
     2112                        DVHD    IEC Standard DV 1125 lines @ 30fps / 1250 lines @ 25fps
     2113                        DVMA    Darim Vision DVMPEG (dummy for MPEG compressor) (www.darvision.com)
     2114                        DVSL    IEC Standard DV compressed in SD (SDL)
     2115                        DVAN    ?DVAN?
     2116                        DVE2    InSoft DVE-2 Videoconferencing
     2117                        dvsd    IEC 61834 and SMPTE 314M DVC/DV Video
     2118                        DVSD    IEC 61834 and SMPTE 314M DVC/DV Video
     2119                        DVX1    Lucent DVX1000SP Video Decoder
     2120                        DVX2    Lucent DVX2000S Video Decoder
     2121                        DVX3    Lucent DVX3000S Video Decoder
     2122                        DX50    DivX v5
     2123                        DXT1    Microsoft DirectX Compressed Texture (DXT1)
     2124                        DXT2    Microsoft DirectX Compressed Texture (DXT2)
     2125                        DXT3    Microsoft DirectX Compressed Texture (DXT3)
     2126                        DXT4    Microsoft DirectX Compressed Texture (DXT4)
     2127                        DXT5    Microsoft DirectX Compressed Texture (DXT5)
     2128                        DXTC    Microsoft DirectX Compressed Texture (DXTC)
     2129                        DXTn    Microsoft DirectX Compressed Texture (DXTn)
     2130                        EM2V    Etymonix MPEG-2 I-frame (www.etymonix.com)
     2131                        EKQ0    Elsa ?EKQ0?
     2132                        ELK0    Elsa ?ELK0?
     2133                        ESCP    Eidos Escape
     2134                        ETV1    eTreppid Video ETV1
     2135                        ETV2    eTreppid Video ETV2
     2136                        ETVC    eTreppid Video ETVC
     2137                        FLIC    Autodesk FLI/FLC Animation
     2138                        FLV1    Sorenson Spark
     2139                        FLV4    On2 TrueMotion VP6
     2140                        FRWT    Darim Vision Forward Motion JPEG (www.darvision.com)
     2141                        FRWU    Darim Vision Forward Uncompressed (www.darvision.com)
     2142                        FLJP    D-Vision Field Encoded Motion JPEG
     2143                        FPS1    FRAPS v1
     2144                        FRWA    SoftLab-Nsk Forward Motion JPEG w/ alpha channel
     2145                        FRWD    SoftLab-Nsk Forward Motion JPEG
     2146                        FVF1    Iterated Systems Fractal Video Frame
     2147                        GLZW    Motion LZW (gabest@freemail.hu)
     2148                        GPEG    Motion JPEG (gabest@freemail.hu)
     2149                        GWLT    Microsoft Greyscale WLT DIB
     2150                        H260    Intel ITU H.260 Videoconferencing
     2151                        H261    Intel ITU H.261 Videoconferencing
     2152                        H262    Intel ITU H.262 Videoconferencing
     2153                        H263    Intel ITU H.263 Videoconferencing
     2154                        H264    Intel ITU H.264 Videoconferencing
     2155                        H265    Intel ITU H.265 Videoconferencing
     2156                        H266    Intel ITU H.266 Videoconferencing
     2157                        H267    Intel ITU H.267 Videoconferencing
     2158                        H268    Intel ITU H.268 Videoconferencing
     2159                        H269    Intel ITU H.269 Videoconferencing
     2160                        HFYU    Huffman Lossless Codec
     2161                        HMCR    Rendition Motion Compensation Format (HMCR)
     2162                        HMRR    Rendition Motion Compensation Format (HMRR)
     2163                        I263    FFmpeg I263 decoder
     2164                        IF09    Indeo YVU9 ("YVU9 with additional delta-frame info after the U plane")
     2165                        IUYV    Interlaced version of UYVY (www.leadtools.com)
     2166                        IY41    Interlaced version of Y41P (www.leadtools.com)
     2167                        IYU1    12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec    IEEE standard
     2168                        IYU2    24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec    IEEE standard
     2169                        IYUV    Planar YUV format (8-bpp Y plane, followed by 8-bpp 2×2 U and V planes)
     2170                        i263    Intel ITU H.263 Videoconferencing (i263)
     2171                        I420    Intel Indeo 4
     2172                        IAN     Intel Indeo 4 (RDX)
     2173                        ICLB    InSoft CellB Videoconferencing
     2174                        IGOR    Power DVD
     2175                        IJPG    Intergraph JPEG
     2176                        ILVC    Intel Layered Video
     2177                        ILVR    ITU-T H.263+
     2178                        IPDV    I-O Data Device Giga AVI DV Codec
     2179                        IR21    Intel Indeo 2.1
     2180                        IRAW    Intel YUV Uncompressed
     2181                        IV30    Intel Indeo 3.0
     2182                        IV31    Intel Indeo 3.1
     2183                        IV32    Ligos Indeo 3.2
     2184                        IV33    Ligos Indeo 3.3
     2185                        IV34    Ligos Indeo 3.4
     2186                        IV35    Ligos Indeo 3.5
     2187                        IV36    Ligos Indeo 3.6
     2188                        IV37    Ligos Indeo 3.7
     2189                        IV38    Ligos Indeo 3.8
     2190                        IV39    Ligos Indeo 3.9
     2191                        IV40    Ligos Indeo Interactive 4.0
     2192                        IV41    Ligos Indeo Interactive 4.1
     2193                        IV42    Ligos Indeo Interactive 4.2
     2194                        IV43    Ligos Indeo Interactive 4.3
     2195                        IV44    Ligos Indeo Interactive 4.4
     2196                        IV45    Ligos Indeo Interactive 4.5
     2197                        IV46    Ligos Indeo Interactive 4.6
     2198                        IV47    Ligos Indeo Interactive 4.7
     2199                        IV48    Ligos Indeo Interactive 4.8
     2200                        IV49    Ligos Indeo Interactive 4.9
     2201                        IV50    Ligos Indeo Interactive 5.0
     2202                        JBYR    Kensington ?JBYR?
     2203                        JPEG    Still Image JPEG DIB
     2204                        JPGL    Pegasus Lossless Motion JPEG
     2205                        KMVC    Team17 Software Karl Morton\'s Video Codec
     2206                        LSVM    Vianet Lighting Strike Vmail (Streaming) (www.vianet.com)
     2207                        LEAD    LEAD Video Codec
     2208                        Ljpg    LEAD MJPEG Codec
     2209                        MDVD    Alex MicroDVD Video (hacked MS MPEG-4) (www.tiasoft.de)
     2210                        MJPA    Morgan Motion JPEG (MJPA) (www.morgan-multimedia.com)
     2211                        MJPB    Morgan Motion JPEG (MJPB) (www.morgan-multimedia.com)
     2212                        MMES    Matrox MPEG-2 I-frame
     2213                        MP2v    Microsoft S-Mpeg 4 version 1 (MP2v)
     2214                        MP42    Microsoft S-Mpeg 4 version 2 (MP42)
     2215                        MP43    Microsoft S-Mpeg 4 version 3 (MP43)
     2216                        MP4S    Microsoft S-Mpeg 4 version 3 (MP4S)
     2217                        MP4V    FFmpeg MPEG-4
     2218                        MPG1    FFmpeg MPEG 1/2
     2219                        MPG2    FFmpeg MPEG 1/2
     2220                        MPG3    FFmpeg DivX ;-) (MS MPEG-4 v3)
     2221                        MPG4    Microsoft MPEG-4
     2222                        MPGI    Sigma Designs MPEG
     2223                        MPNG    PNG images decoder
     2224                        MSS1    Microsoft Windows Screen Video
     2225                        MSZH    LCL (Lossless Codec Library) (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm)
     2226                        M261    Microsoft H.261
     2227                        M263    Microsoft H.263
     2228                        M4S2    Microsoft Fully Compliant MPEG-4 v2 simple profile (M4S2)
     2229                        m4s2    Microsoft Fully Compliant MPEG-4 v2 simple profile (m4s2)
     2230                        MC12    ATI Motion Compensation Format (MC12)
     2231                        MCAM    ATI Motion Compensation Format (MCAM)
     2232                        MJ2C    Morgan Multimedia Motion JPEG2000
     2233                        mJPG    IBM Motion JPEG w/ Huffman Tables
     2234                        MJPG    Microsoft Motion JPEG DIB
     2235                        MP42    Microsoft MPEG-4 (low-motion)
     2236                        MP43    Microsoft MPEG-4 (fast-motion)
     2237                        MP4S    Microsoft MPEG-4 (MP4S)
     2238                        mp4s    Microsoft MPEG-4 (mp4s)
     2239                        MPEG    Chromatic Research MPEG-1 Video I-Frame
     2240                        MPG4    Microsoft MPEG-4 Video High Speed Compressor
     2241                        MPGI    Sigma Designs MPEG
     2242                        MRCA    FAST Multimedia Martin Regen Codec
     2243                        MRLE    Microsoft Run Length Encoding
     2244                        MSVC    Microsoft Video 1
     2245                        MTX1    Matrox ?MTX1?
     2246                        MTX2    Matrox ?MTX2?
     2247                        MTX3    Matrox ?MTX3?
     2248                        MTX4    Matrox ?MTX4?
     2249                        MTX5    Matrox ?MTX5?
     2250                        MTX6    Matrox ?MTX6?
     2251                        MTX7    Matrox ?MTX7?
     2252                        MTX8    Matrox ?MTX8?
     2253                        MTX9    Matrox ?MTX9?
     2254                        MV12    Motion Pixels Codec (old)
     2255                        MWV1    Aware Motion Wavelets
     2256                        nAVI    SMR Codec (hack of Microsoft MPEG-4) (IRC #shadowrealm)
     2257                        NT00    NewTek LightWave HDTV YUV w/ Alpha (www.newtek.com)
     2258                        NUV1    NuppelVideo
     2259                        NTN1    Nogatech Video Compression 1
     2260                        NVS0    nVidia GeForce Texture (NVS0)
     2261                        NVS1    nVidia GeForce Texture (NVS1)
     2262                        NVS2    nVidia GeForce Texture (NVS2)
     2263                        NVS3    nVidia GeForce Texture (NVS3)
     2264                        NVS4    nVidia GeForce Texture (NVS4)
     2265                        NVS5    nVidia GeForce Texture (NVS5)
     2266                        NVT0    nVidia GeForce Texture (NVT0)
     2267                        NVT1    nVidia GeForce Texture (NVT1)
     2268                        NVT2    nVidia GeForce Texture (NVT2)
     2269                        NVT3    nVidia GeForce Texture (NVT3)
     2270                        NVT4    nVidia GeForce Texture (NVT4)
     2271                        NVT5    nVidia GeForce Texture (NVT5)
     2272                        PIXL    MiroXL, Pinnacle PCTV
     2273                        PDVC    I-O Data Device Digital Video Capture DV codec
     2274                        PGVV    Radius Video Vision
     2275                        PHMO    IBM Photomotion
     2276                        PIM1    MPEG Realtime (Pinnacle Cards)
     2277                        PIM2    Pegasus Imaging ?PIM2?
     2278                        PIMJ    Pegasus Imaging Lossless JPEG
     2279                        PVEZ    Horizons Technology PowerEZ
     2280                        PVMM    PacketVideo Corporation MPEG-4
     2281                        PVW2    Pegasus Imaging Wavelet Compression
     2282                        Q1.0    Q-Team\'s QPEG 1.0 (www.q-team.de)
     2283                        Q1.1    Q-Team\'s QPEG 1.1 (www.q-team.de)
     2284                        QPEG    Q-Team QPEG 1.0
     2285                        qpeq    Q-Team QPEG 1.1
     2286                        RGB     Raw BGR32
     2287                        RGBA    Raw RGB w/ Alpha
     2288                        RMP4    REALmagic MPEG-4 (unauthorized XVID copy) (www.sigmadesigns.com)
     2289                        ROQV    Id RoQ File Video Decoder
     2290                        RPZA    Quicktime Apple Video (RPZA)
     2291                        RUD0    Rududu video codec (http://rududu.ifrance.com/rududu/)
     2292                        RV10    RealVideo 1.0 (aka RealVideo 5.0)
     2293                        RV13    RealVideo 1.0 (RV13)
     2294                        RV20    RealVideo G2
     2295                        RV30    RealVideo 8
     2296                        RV40    RealVideo 9
     2297                        RGBT    Raw RGB w/ Transparency
     2298                        RLE     Microsoft Run Length Encoder
     2299                        RLE4    Run Length Encoded (4bpp, 16-color)
     2300                        RLE8    Run Length Encoded (8bpp, 256-color)
     2301                        RT21    Intel Indeo RealTime Video 2.1
     2302                        rv20    RealVideo G2
     2303                        rv30    RealVideo 8
     2304                        RVX     Intel RDX (RVX )
     2305                        SMC     Apple Graphics (SMC )
     2306                        SP54    Logitech Sunplus Sp54 Codec for Mustek GSmart Mini 2
     2307                        SPIG    Radius Spigot
     2308                        SVQ3    Sorenson Video 3 (Apple Quicktime 5)
     2309                        s422    Tekram VideoCap C210 YUV 4:2:2
     2310                        SDCC    Sun Communication Digital Camera Codec
     2311                        SFMC    CrystalNet Surface Fitting Method
     2312                        SMSC    Radius SMSC
     2313                        SMSD    Radius SMSD
     2314                        smsv    WorldConnect Wavelet Video
     2315                        SPIG    Radius Spigot
     2316                        SPLC    Splash Studios ACM Audio Codec (www.splashstudios.net)
     2317                        SQZ2    Microsoft VXTreme Video Codec V2
     2318                        STVA    ST Microelectronics CMOS Imager Data (Bayer)
     2319                        STVB    ST Microelectronics CMOS Imager Data (Nudged Bayer)
     2320                        STVC    ST Microelectronics CMOS Imager Data (Bunched)
     2321                        STVX    ST Microelectronics CMOS Imager Data (Extended CODEC Data Format)
     2322                        STVY    ST Microelectronics CMOS Imager Data (Extended CODEC Data Format with Correction Data)
     2323                        SV10    Sorenson Video R1
     2324                        SVQ1    Sorenson Video
     2325                        T420    Toshiba YUV 4:2:0
     2326                        TM2A    Duck TrueMotion Archiver 2.0 (www.duck.com)
     2327                        TVJP    Pinnacle/Truevision Targa 2000 board (TVJP)
     2328                        TVMJ    Pinnacle/Truevision Targa 2000 board (TVMJ)
     2329                        TY0N    Tecomac Low-Bit Rate Codec (www.tecomac.com)
     2330                        TY2C    Trident Decompression Driver
     2331                        TLMS    TeraLogic Motion Intraframe Codec (TLMS)
     2332                        TLST    TeraLogic Motion Intraframe Codec (TLST)
     2333                        TM20    Duck TrueMotion 2.0
     2334                        TM2X    Duck TrueMotion 2X
     2335                        TMIC    TeraLogic Motion Intraframe Codec (TMIC)
     2336                        TMOT    Horizons Technology TrueMotion S
     2337                        tmot    Horizons TrueMotion Video Compression
     2338                        TR20    Duck TrueMotion RealTime 2.0
     2339                        TSCC    TechSmith Screen Capture Codec
     2340                        TV10    Tecomac Low-Bit Rate Codec
     2341                        TY2N    Trident ?TY2N?
     2342                        U263    UB Video H.263/H.263+/H.263++ Decoder
     2343                        UMP4    UB Video MPEG 4 (www.ubvideo.com)
     2344                        UYNV    Nvidia UYVY packed 4:2:2
     2345                        UYVP    Evans & Sutherland YCbCr 4:2:2 extended precision
     2346                        UCOD    eMajix.com ClearVideo
     2347                        ULTI    IBM Ultimotion
     2348                        UYVY    UYVY packed 4:2:2
     2349                        V261    Lucent VX2000S
     2350                        VIFP    VFAPI Reader Codec (www.yks.ne.jp/~hori/)
     2351                        VIV1    FFmpeg H263+ decoder
     2352                        VIV2    Vivo H.263
     2353                        VQC2    Vector-quantised codec 2 (research) http://eprints.ecs.soton.ac.uk/archive/00001310/01/VTC97-js.pdf)
     2354                        VTLP    Alaris VideoGramPiX
     2355                        VYU9    ATI YUV (VYU9)
     2356                        VYUY    ATI YUV (VYUY)
     2357                        V261    Lucent VX2000S
     2358                        V422    Vitec Multimedia 24-bit YUV 4:2:2 Format
     2359                        V655    Vitec Multimedia 16-bit YUV 4:2:2 Format
     2360                        VCR1    ATI Video Codec 1
     2361                        VCR2    ATI Video Codec 2
     2362                        VCR3    ATI VCR 3.0
     2363                        VCR4    ATI VCR 4.0
     2364                        VCR5    ATI VCR 5.0
     2365                        VCR6    ATI VCR 6.0
     2366                        VCR7    ATI VCR 7.0
     2367                        VCR8    ATI VCR 8.0
     2368                        VCR9    ATI VCR 9.0
     2369                        VDCT    Vitec Multimedia Video Maker Pro DIB
     2370                        VDOM    VDOnet VDOWave
     2371                        VDOW    VDOnet VDOLive (H.263)
     2372                        VDTZ    Darim Vison VideoTizer YUV
     2373                        VGPX    Alaris VideoGramPiX
     2374                        VIDS    Vitec Multimedia YUV 4:2:2 CCIR 601 for V422
     2375                        VIVO    Vivo H.263 v2.00
     2376                        vivo    Vivo H.263
     2377                        VIXL    Miro/Pinnacle Video XL
     2378                        VLV1    VideoLogic/PURE Digital Videologic Capture
     2379                        VP30    On2 VP3.0
     2380                        VP31    On2 VP3.1
     2381                        VP6F    On2 TrueMotion VP6
     2382                        VX1K    Lucent VX1000S Video Codec
     2383                        VX2K    Lucent VX2000S Video Codec
     2384                        VXSP    Lucent VX1000SP Video Codec
     2385                        WBVC    Winbond W9960
     2386                        WHAM    Microsoft Video 1 (WHAM)
     2387                        WINX    Winnov Software Compression
     2388                        WJPG    AverMedia Winbond JPEG
     2389                        WMV1    Windows Media Video V7
     2390                        WMV2    Windows Media Video V8
     2391                        WMV3    Windows Media Video V9
     2392                        WNV1    Winnov Hardware Compression
     2393                        XYZP    Extended PAL format XYZ palette (www.riff.org)
     2394                        x263    Xirlink H.263
     2395                        XLV0    NetXL Video Decoder
     2396                        XMPG    Xing MPEG (I-Frame only)
     2397                        XVID    XviD MPEG-4 (www.xvid.org)
     2398                        XXAN    ?XXAN?
     2399                        YU92    Intel YUV (YU92)
     2400                        YUNV    Nvidia Uncompressed YUV 4:2:2
     2401                        YUVP    Extended PAL format YUV palette (www.riff.org)
     2402                        Y211    YUV 2:1:1 Packed
     2403                        Y411    YUV 4:1:1 Packed
     2404                        Y41B    Weitek YUV 4:1:1 Planar
     2405                        Y41P    Brooktree PC1 YUV 4:1:1 Packed
     2406                        Y41T    Brooktree PC1 YUV 4:1:1 with transparency
     2407                        Y42B    Weitek YUV 4:2:2 Planar
     2408                        Y42T    Brooktree UYUV 4:2:2 with transparency
     2409                        Y422    ADS Technologies Copy of UYVY used in Pyro WebCam firewire camera
     2410                        Y800    Simple, single Y plane for monochrome images
     2411                        Y8      Grayscale video
     2412                        YC12    Intel YUV 12 codec
     2413                        YUV8    Winnov Caviar YUV8
     2414                        YUV9    Intel YUV9
     2415                        YUY2    Uncompressed YUV 4:2:2
     2416                        YUYV    Canopus YUV
     2417                        YV12    YVU12 Planar
     2418                        YVU9    Intel YVU9 Planar (8-bpp Y plane, followed by 8-bpp 4x4 U and V planes)
     2419                        YVYU    YVYU 4:2:2 Packed
     2420                        ZLIB    Lossless Codec Library zlib compression (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm)
     2421                        ZPEG    Metheus Video Zipper
     2422
     2423                */
     2424
     2425                return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc');
     2426        }
     2427
     2428        private function EitherEndian2Int($byteword, $signed=false) {
     2429                if ($this->getid3->info['fileformat'] == 'riff') {
     2430                        return getid3_lib::LittleEndian2Int($byteword, $signed);
     2431                }
     2432                return getid3_lib::BigEndian2Int($byteword, false, $signed);
     2433        }
     2434
     2435}
  • new file wp-includes/ID3/module.audio.ac3.php

    diff --git wp-includes/ID3/module.audio.ac3.php wp-includes/ID3/module.audio.ac3.php
    new file mode 100644
    index 0000000..9834feb
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio.ac3.php                                        //
     11// module for analyzing AC-3 (aka Dolby Digital) audio files   //
     12// dependencies: NONE                                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16
     17class getid3_ac3 extends getid3_handler
     18{
     19    private $AC3header = array();
     20    private $BSIoffset = 0;
     21
     22    const syncword = "\x0B\x77";
     23
     24        public function Analyze() {
     25                $info = &$this->getid3->info;
     26
     27                ///AH
     28                $info['ac3']['raw']['bsi'] = array();
     29                $thisfile_ac3              = &$info['ac3'];
     30                $thisfile_ac3_raw          = &$thisfile_ac3['raw'];
     31                $thisfile_ac3_raw_bsi      = &$thisfile_ac3_raw['bsi'];
     32
     33
     34                // http://www.atsc.org/standards/a_52a.pdf
     35
     36                $info['fileformat'] = 'ac3';
     37
     38                // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
     39                // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
     40                // new audio samples per channel. A synchronization information (SI) header at the beginning
     41                // of each frame contains information needed to acquire and maintain synchronization. A
     42                // bit stream information (BSI) header follows SI, and contains parameters describing the coded
     43                // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
     44                // end of each frame is an error check field that includes a CRC word for error detection. An
     45                // additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
     46                //
     47                // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
     48
     49                // syncinfo() {
     50                //       syncword    16
     51                //       crc1        16
     52                //       fscod        2
     53                //       frmsizecod   6
     54                // } /* end of syncinfo */
     55
     56                $this->fseek($info['avdataoffset']);
     57                $this->AC3header['syncinfo'] = $this->fread(5);
     58
     59                if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) {
     60                        $thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword;
     61                        $offset = 2;
     62                } else {
     63                        if (!$this->isDependencyFor('matroska')) {
     64                                unset($info['fileformat'], $info['ac3']);
     65                                return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"');
     66                        }
     67                        $offset = 0;
     68                        $this->fseek(-2, SEEK_CUR);
     69                }
     70
     71                $info['audio']['dataformat']   = 'ac3';
     72                $info['audio']['bitrate_mode'] = 'cbr';
     73                $info['audio']['lossless']     = false;
     74
     75                $thisfile_ac3_raw['synchinfo']['crc1']       = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2));
     76                $ac3_synchinfo_fscod_frmsizecod              = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1));
     77                $thisfile_ac3_raw['synchinfo']['fscod']      = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6;
     78                $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F);
     79
     80                $thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
     81                if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
     82                        $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
     83                }
     84
     85                $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
     86                $thisfile_ac3['bitrate']      = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
     87                $info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
     88
     89                $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15));
     90                $ac3_bsi_offset = 0;
     91
     92                $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
     93                if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
     94                        // Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
     95                        // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
     96                        // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
     97                        $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8');
     98                    unset($info['ac3']);
     99                        return false;
     100                }
     101
     102                $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
     103                $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
     104
     105                $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
     106                $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
     107                foreach($ac3_coding_mode as $key => $value) {
     108                        $thisfile_ac3[$key] = $value;
     109                }
     110                switch ($thisfile_ac3_raw_bsi['acmod']) {
     111                        case 0:
     112                        case 1:
     113                                $info['audio']['channelmode'] = 'mono';
     114                                break;
     115                        case 3:
     116                        case 4:
     117                                $info['audio']['channelmode'] = 'stereo';
     118                                break;
     119                        default:
     120                                $info['audio']['channelmode'] = 'surround';
     121                                break;
     122                }
     123                $info['audio']['channels'] = $thisfile_ac3['num_channels'];
     124
     125                if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
     126                        // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
     127                        $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
     128                        $thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
     129                }
     130
     131                if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
     132                        // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
     133                        $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
     134                        $thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
     135                }
     136
     137                if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
     138                        // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
     139                        $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
     140                        $thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
     141                }
     142
     143                $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
     144                $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
     145                if ($thisfile_ac3_raw_bsi['lfeon']) {
     146                        //$info['audio']['channels']++;
     147                        $info['audio']['channels'] .= '.1';
     148                }
     149
     150                $thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
     151
     152                // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
     153                // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
     154                $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
     155                $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
     156
     157                $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
     158                if ($thisfile_ac3_raw_bsi['compre_flag']) {
     159                        $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
     160                        $thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
     161                }
     162
     163                $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
     164                if ($thisfile_ac3_raw_bsi['langcode_flag']) {
     165                        $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
     166                }
     167
     168                $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
     169                if ($thisfile_ac3_raw_bsi['audprodie']) {
     170                        $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
     171                        $thisfile_ac3_raw_bsi['roomtyp']  = $this->readHeaderBSI(2);
     172
     173                        $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
     174                        $thisfile_ac3['room_type']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
     175                }
     176
     177                if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) {
     178                        // If acmod is 0, then two completely independent program channels (dual mono)
     179                        // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
     180                        // a number of additional items are present in BSI or audblk to fully describe Ch2.
     181
     182                        // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
     183                        // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
     184                        $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
     185                        $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';
     186
     187                        $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
     188                        if ($thisfile_ac3_raw_bsi['compre_flag2']) {
     189                                $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
     190                                $thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
     191                        }
     192
     193                        $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
     194                        if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
     195                                $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
     196                        }
     197
     198                        $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
     199                        if ($thisfile_ac3_raw_bsi['audprodie2']) {
     200                                $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
     201                                $thisfile_ac3_raw_bsi['roomtyp2']  = $this->readHeaderBSI(2);
     202
     203                                $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
     204                                $thisfile_ac3['room_type2']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
     205                        }
     206
     207                }
     208
     209                $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
     210
     211                $thisfile_ac3_raw_bsi['original']  = (bool) $this->readHeaderBSI(1);
     212
     213                $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
     214                if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
     215                        $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
     216                }
     217
     218                $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
     219                if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
     220                        $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
     221                }
     222
     223                $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
     224                if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
     225                        $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
     226
     227                        $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
     228
     229                        $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
     230                        $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
     231                }
     232
     233                return true;
     234        }
     235
     236        private function readHeaderBSI($length) {
     237                $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
     238                $this->BSIoffset += $length;
     239
     240                return bindec($data);
     241        }
     242
     243        public static function sampleRateCodeLookup($fscod) {
     244                static $sampleRateCodeLookup = array(
     245                        0 => 48000,
     246                        1 => 44100,
     247                        2 => 32000,
     248                        3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
     249                );
     250                return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false);
     251        }
     252
     253        public static function serviceTypeLookup($bsmod, $acmod) {
     254                static $serviceTypeLookup = array();
     255                if (empty($serviceTypeLookup)) {
     256                        for ($i = 0; $i <= 7; $i++) {
     257                                $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
     258                                $serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
     259                                $serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
     260                                $serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
     261                                $serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
     262                                $serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
     263                                $serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
     264                        }
     265
     266                        $serviceTypeLookup[7][1]      = 'associated service: voice over (VO)';
     267                        for ($i = 2; $i <= 7; $i++) {
     268                                $serviceTypeLookup[7][$i] = 'main audio service: karaoke';
     269                        }
     270                }
     271                return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false);
     272        }
     273
     274        public static function audioCodingModeLookup($acmod) {
     275                // array(channel configuration, # channels (not incl LFE), channel order)
     276                static $audioCodingModeLookup = array (
     277                        0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
     278                        1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
     279                        2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
     280                        3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
     281                        4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
     282                        5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
     283                        6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
     284                        7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'),
     285                );
     286                return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false);
     287        }
     288
     289        public static function centerMixLevelLookup($cmixlev) {
     290                static $centerMixLevelLookup;
     291                if (empty($centerMixLevelLookup)) {
     292                        $centerMixLevelLookup = array(
     293                                0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB)
     294                                1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB)
     295                                2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB)
     296                                3 => 'reserved'
     297                        );
     298                }
     299                return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false);
     300        }
     301
     302        public static function surroundMixLevelLookup($surmixlev) {
     303                static $surroundMixLevelLookup;
     304                if (empty($surroundMixLevelLookup)) {
     305                        $surroundMixLevelLookup = array(
     306                                0 => pow(2, -3.0 / 6),
     307                                1 => pow(2, -6.0 / 6),
     308                                2 => 0,
     309                                3 => 'reserved'
     310                        );
     311                }
     312                return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false);
     313        }
     314
     315        public static function dolbySurroundModeLookup($dsurmod) {
     316                static $dolbySurroundModeLookup = array(
     317                        0 => 'not indicated',
     318                        1 => 'Not Dolby Surround encoded',
     319                        2 => 'Dolby Surround encoded',
     320                        3 => 'reserved'
     321                );
     322                return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false);
     323        }
     324
     325        public static function channelsEnabledLookup($acmod, $lfeon) {
     326                $lookup = array(
     327                        'ch1'=>(bool) ($acmod == 0),
     328                        'ch2'=>(bool) ($acmod == 0),
     329                        'left'=>(bool) ($acmod > 1),
     330                        'right'=>(bool) ($acmod > 1),
     331                        'center'=>(bool) ($acmod & 0x01),
     332                        'surround_mono'=>false,
     333                        'surround_left'=>false,
     334                        'surround_right'=>false,
     335                        'lfe'=>$lfeon);
     336                switch ($acmod) {
     337                        case 4:
     338                        case 5:
     339                                $lookup['surround_mono']  = true;
     340                                break;
     341                        case 6:
     342                        case 7:
     343                                $lookup['surround_left']  = true;
     344                                $lookup['surround_right'] = true;
     345                                break;
     346                }
     347                return $lookup;
     348        }
     349
     350        public static function heavyCompression($compre) {
     351                // The first four bits indicate gain changes in 6.02dB increments which can be
     352                // implemented with an arithmetic shift operation. The following four bits
     353                // indicate linear gain changes, and require a 5-bit multiply.
     354                // We will represent the two 4-bit fields of compr as follows:
     355                //   X0 X1 X2 X3 . Y4 Y5 Y6 Y7
     356                // The meaning of the X values is most simply described by considering X to represent a 4-bit
     357                // signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
     358                // following table shows this in detail.
     359
     360                // Meaning of 4 msb of compr
     361                //  7    +48.16 dB
     362                //  6    +42.14 dB
     363                //  5    +36.12 dB
     364                //  4    +30.10 dB
     365                //  3    +24.08 dB
     366                //  2    +18.06 dB
     367                //  1    +12.04 dB
     368                //  0     +6.02 dB
     369                // -1         0 dB
     370                // -2     -6.02 dB
     371                // -3    -12.04 dB
     372                // -4    -18.06 dB
     373                // -5    -24.08 dB
     374                // -6    -30.10 dB
     375                // -7    -36.12 dB
     376                // -8    -42.14 dB
     377
     378                $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
     379                if ($fourbit{0} == '1') {
     380                        $log_gain = -8 + bindec(substr($fourbit, 1));
     381                } else {
     382                        $log_gain = bindec(substr($fourbit, 1));
     383                }
     384                $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);
     385
     386                // The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to
     387                // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
     388                // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
     389                // changes from -0.28 dB to -6.02 dB.
     390
     391                $lin_gain = (16 + ($compre & 0x0F)) / 32;
     392
     393                // The combination of X and Y values allows compr to indicate gain changes from
     394                //  48.16 - 0.28 = +47.89 dB, to
     395                // -42.14 - 6.02 = -48.16 dB.
     396
     397                return $log_gain - $lin_gain;
     398        }
     399
     400        public static function roomTypeLookup($roomtyp) {
     401                static $roomTypeLookup = array(
     402                        0 => 'not indicated',
     403                        1 => 'large room, X curve monitor',
     404                        2 => 'small room, flat monitor',
     405                        3 => 'reserved'
     406                );
     407                return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false);
     408        }
     409
     410        public static function frameSizeLookup($frmsizecod, $fscod) {
     411                $padding     = (bool) ($frmsizecod % 2);
     412                $framesizeid =   floor($frmsizecod / 2);
     413
     414                static $frameSizeLookup = array();
     415                if (empty($frameSizeLookup)) {
     416                        $frameSizeLookup = array (
     417                                0  => array(128, 138, 192),
     418                                1  => array(40, 160, 174, 240),
     419                                2  => array(48, 192, 208, 288),
     420                                3  => array(56, 224, 242, 336),
     421                                4  => array(64, 256, 278, 384),
     422                                5  => array(80, 320, 348, 480),
     423                                6  => array(96, 384, 416, 576),
     424                                7  => array(112, 448, 486, 672),
     425                                8  => array(128, 512, 556, 768),
     426                                9  => array(160, 640, 696, 960),
     427                                10 => array(192, 768, 834, 1152),
     428                                11 => array(224, 896, 974, 1344),
     429                                12 => array(256, 1024, 1114, 1536),
     430                                13 => array(320, 1280, 1392, 1920),
     431                                14 => array(384, 1536, 1670, 2304),
     432                                15 => array(448, 1792, 1950, 2688),
     433                                16 => array(512, 2048, 2228, 3072),
     434                                17 => array(576, 2304, 2506, 3456),
     435                                18 => array(640, 2560, 2786, 3840)
     436                        );
     437                }
     438                if (($fscod == 1) && $padding) {
     439                        // frame lengths are padded by 1 word (16 bits) at 44100
     440                        $frameSizeLookup[$frmsizecod] += 2;
     441                }
     442                return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false);
     443        }
     444
     445        public static function bitrateLookup($frmsizecod) {
     446                $framesizeid =   floor($frmsizecod / 2);
     447
     448                static $bitrateLookup = array(
     449                        0  => 32000,
     450                        1  => 40000,
     451                        2  => 48000,
     452                        3  => 56000,
     453                        4  => 64000,
     454                        5  => 80000,
     455                        6  => 96000,
     456                        7  => 112000,
     457                        8  => 128000,
     458                        9  => 160000,
     459                        10 => 192000,
     460                        11 => 224000,
     461                        12 => 256000,
     462                        13 => 320000,
     463                        14 => 384000,
     464                        15 => 448000,
     465                        16 => 512000,
     466                        17 => 576000,
     467                        18 => 640000
     468                );
     469                return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false);
     470        }
     471
     472
     473}
  • new file wp-includes/ID3/module.audio.dts.php

    diff --git wp-includes/ID3/module.audio.dts.php wp-includes/ID3/module.audio.dts.php
    new file mode 100644
    index 0000000..79982cc
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio.dts.php                                        //
     11// module for analyzing DTS Audio files                        //
     12// dependencies: NONE                                          //
     13//                                                             //
     14/////////////////////////////////////////////////////////////////
     15
     16
     17/**
     18* @tutorial http://wiki.multimedia.cx/index.php?title=DTS
     19*/
     20class getid3_dts extends getid3_handler
     21{
     22        /**
     23        * Default DTS syncword used in native .cpt or .dts formats
     24        */
     25    const syncword = "\x7F\xFE\x80\x01";
     26
     27        private $readBinDataOffset = 0;
     28
     29    /**
     30    * Possible syncwords indicating bitstream encoding
     31    */
     32    public static $syncwords = array(
     33        0 => "\x7F\xFE\x80\x01",  // raw big-endian
     34        1 => "\xFE\x7F\x01\x80",  // raw little-endian
     35        2 => "\x1F\xFF\xE8\x00",  // 14-bit big-endian
     36        3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian
     37
     38        public function Analyze() {
     39                $info = &$this->getid3->info;
     40                $info['fileformat'] = 'dts';
     41
     42                $this->fseek($info['avdataoffset']);
     43                $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes
     44
     45                // check syncword
     46                $sync = substr($DTSheader, 0, 4);
     47        if (($encoding = array_search($sync, self::$syncwords)) !== false) {
     48
     49                $info['dts']['raw']['magic'] = $sync;
     50                        $this->readBinDataOffset = 32;
     51
     52        } elseif ($this->isDependencyFor('matroska')) {
     53
     54                        // Matroska contains DTS without syncword encoded as raw big-endian format
     55                        $encoding = 0;
     56                        $this->readBinDataOffset = 0;
     57
     58        } else {
     59
     60                        unset($info['fileformat']);
     61                        return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"');
     62
     63                }
     64
     65                // decode header
     66                $fhBS = '';
     67                for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) {
     68                        switch ($encoding) {
     69                                case 0: // raw big-endian
     70                                        $fhBS .=        getid3_lib::BigEndian2Bin(       substr($DTSheader, $word_offset, 2) );
     71                                        break;
     72                                case 1: // raw little-endian
     73                                        $fhBS .=        getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2)));
     74                                        break;
     75                                case 2: // 14-bit big-endian
     76                                        $fhBS .= substr(getid3_lib::BigEndian2Bin(       substr($DTSheader, $word_offset, 2) ), 2, 14);
     77                                        break;
     78                                case 3: // 14-bit little-endian
     79                                        $fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14);
     80                                        break;
     81                        }
     82                }
     83
     84                $info['dts']['raw']['frame_type']             =        $this->readBinData($fhBS,  1);
     85                $info['dts']['raw']['deficit_samples']        =        $this->readBinData($fhBS,  5);
     86                $info['dts']['flags']['crc_present']          = (bool) $this->readBinData($fhBS,  1);
     87                $info['dts']['raw']['pcm_sample_blocks']      =        $this->readBinData($fhBS,  7);
     88                $info['dts']['raw']['frame_byte_size']        =        $this->readBinData($fhBS, 14);
     89                $info['dts']['raw']['channel_arrangement']    =        $this->readBinData($fhBS,  6);
     90                $info['dts']['raw']['sample_frequency']       =        $this->readBinData($fhBS,  4);
     91                $info['dts']['raw']['bitrate']                =        $this->readBinData($fhBS,  5);
     92                $info['dts']['flags']['embedded_downmix']     = (bool) $this->readBinData($fhBS,  1);
     93                $info['dts']['flags']['dynamicrange']         = (bool) $this->readBinData($fhBS,  1);
     94                $info['dts']['flags']['timestamp']            = (bool) $this->readBinData($fhBS,  1);
     95                $info['dts']['flags']['auxdata']              = (bool) $this->readBinData($fhBS,  1);
     96                $info['dts']['flags']['hdcd']                 = (bool) $this->readBinData($fhBS,  1);
     97                $info['dts']['raw']['extension_audio']        =        $this->readBinData($fhBS,  3);
     98                $info['dts']['flags']['extended_coding']      = (bool) $this->readBinData($fhBS,  1);
     99                $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS,  1);
     100                $info['dts']['raw']['lfe_effects']            =        $this->readBinData($fhBS,  2);
     101                $info['dts']['flags']['predictor_history']    = (bool) $this->readBinData($fhBS,  1);
     102                if ($info['dts']['flags']['crc_present']) {
     103                        $info['dts']['raw']['crc16']              =        $this->readBinData($fhBS, 16);
     104                }
     105                $info['dts']['flags']['mri_perfect_reconst']  = (bool) $this->readBinData($fhBS,  1);
     106                $info['dts']['raw']['encoder_soft_version']   =        $this->readBinData($fhBS,  4);
     107                $info['dts']['raw']['copy_history']           =        $this->readBinData($fhBS,  2);
     108                $info['dts']['raw']['bits_per_sample']        =        $this->readBinData($fhBS,  2);
     109                $info['dts']['flags']['surround_es']          = (bool) $this->readBinData($fhBS,  1);
     110                $info['dts']['flags']['front_sum_diff']       = (bool) $this->readBinData($fhBS,  1);
     111                $info['dts']['flags']['surround_sum_diff']    = (bool) $this->readBinData($fhBS,  1);
     112                $info['dts']['raw']['dialog_normalization']   =        $this->readBinData($fhBS,  4);
     113
     114
     115                $info['dts']['bitrate']              = self::bitrateLookup($info['dts']['raw']['bitrate']);
     116                $info['dts']['bits_per_sample']      = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
     117                $info['dts']['sample_rate']          = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
     118                $info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
     119                $info['dts']['flags']['lossless']    = (($info['dts']['raw']['bitrate'] == 31) ? true  : false);
     120                $info['dts']['bitrate_mode']         = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
     121                $info['dts']['channels']             = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
     122                $info['dts']['channel_arrangement']  = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);
     123
     124                $info['audio']['dataformat']          = 'dts';
     125                $info['audio']['lossless']            = $info['dts']['flags']['lossless'];
     126                $info['audio']['bitrate_mode']        = $info['dts']['bitrate_mode'];
     127                $info['audio']['bits_per_sample']     = $info['dts']['bits_per_sample'];
     128                $info['audio']['sample_rate']         = $info['dts']['sample_rate'];
     129                $info['audio']['channels']            = $info['dts']['channels'];
     130                $info['audio']['bitrate']             = $info['dts']['bitrate'];
     131                if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) {
     132                        $info['playtime_seconds']         = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
     133                        if (($encoding == 2) || ($encoding == 3)) {
     134                                // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate
     135                                $info['playtime_seconds'] *= (14 / 16);
     136                        }
     137                }
     138                return true;
     139        }
     140
     141        private function readBinData($bin, $length) {
     142                $data = substr($bin, $this->readBinDataOffset, $length);
     143                $this->readBinDataOffset += $length;
     144
     145                return bindec($data);
     146        }
     147
     148        public static function bitrateLookup($index) {
     149                static $lookup = array(
     150                        0  => 32000,
     151                        1  => 56000,
     152                        2  => 64000,
     153                        3  => 96000,
     154                        4  => 112000,
     155                        5  => 128000,
     156                        6  => 192000,
     157                        7  => 224000,
     158                        8  => 256000,
     159                        9  => 320000,
     160                        10 => 384000,
     161                        11 => 448000,
     162                        12 => 512000,
     163                        13 => 576000,
     164                        14 => 640000,
     165                        15 => 768000,
     166                        16 => 960000,
     167                        17 => 1024000,
     168                        18 => 1152000,
     169                        19 => 1280000,
     170                        20 => 1344000,
     171                        21 => 1408000,
     172                        22 => 1411200,
     173                        23 => 1472000,
     174                        24 => 1536000,
     175                        25 => 1920000,
     176                        26 => 2048000,
     177                        27 => 3072000,
     178                        28 => 3840000,
     179                        29 => 'open',
     180                        30 => 'variable',
     181                        31 => 'lossless',
     182                );
     183                return (isset($lookup[$index]) ? $lookup[$index] : false);
     184        }
     185
     186        public static function sampleRateLookup($index) {
     187                static $lookup = array(
     188                        0  => 'invalid',
     189                        1  => 8000,
     190                        2  => 16000,
     191                        3  => 32000,
     192                        4  => 'invalid',
     193                        5  => 'invalid',
     194                        6  => 11025,
     195                        7  => 22050,
     196                        8  => 44100,
     197                        9  => 'invalid',
     198                        10 => 'invalid',
     199                        11 => 12000,
     200                        12 => 24000,
     201                        13 => 48000,
     202                        14 => 'invalid',
     203                        15 => 'invalid',
     204                );
     205                return (isset($lookup[$index]) ? $lookup[$index] : false);
     206        }
     207
     208        public static function bitPerSampleLookup($index) {
     209                static $lookup = array(
     210                        0  => 16,
     211                        1  => 20,
     212                        2  => 24,
     213                        3  => 24,
     214                );
     215                return (isset($lookup[$index]) ? $lookup[$index] : false);
     216        }
     217
     218        public static function numChannelsLookup($index) {
     219                switch ($index) {
     220                        case 0:
     221                                return 1;
     222                                break;
     223                        case 1:
     224                        case 2:
     225                        case 3:
     226                        case 4:
     227                                return 2;
     228                                break;
     229                        case 5:
     230                        case 6:
     231                                return 3;
     232                                break;
     233                        case 7:
     234                        case 8:
     235                                return 4;
     236                                break;
     237                        case 9:
     238                                return 5;
     239                                break;
     240                        case 10:
     241                        case 11:
     242                        case 12:
     243                                return 6;
     244                                break;
     245                        case 13:
     246                                return 7;
     247                                break;
     248                        case 14:
     249                        case 15:
     250                                return 8;
     251                                break;
     252                }
     253                return false;
     254        }
     255
     256        public static function channelArrangementLookup($index) {
     257                static $lookup = array(
     258                        0  => 'A',
     259                        1  => 'A + B (dual mono)',
     260                        2  => 'L + R (stereo)',
     261                        3  => '(L+R) + (L-R) (sum-difference)',
     262                        4  => 'LT + RT (left and right total)',
     263                        5  => 'C + L + R',
     264                        6  => 'L + R + S',
     265                        7  => 'C + L + R + S',
     266                        8  => 'L + R + SL + SR',
     267                        9  => 'C + L + R + SL + SR',
     268                        10 => 'CL + CR + L + R + SL + SR',
     269                        11 => 'C + L + R+ LR + RR + OV',
     270                        12 => 'CF + CR + LF + RF + LR + RR',
     271                        13 => 'CL + C + CR + L + R + SL + SR',
     272                        14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
     273                        15 => 'CL + C+ CR + L + R + SL + S + SR',
     274                );
     275                return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined');
     276        }
     277
     278        public static function dialogNormalization($index, $version) {
     279                switch ($version) {
     280                        case 7:
     281                                return 0 - $index;
     282                                break;
     283                        case 6:
     284                                return 0 - 16 - $index;
     285                                break;
     286                }
     287                return false;
     288        }
     289
     290}
  • new file wp-includes/ID3/module.audio.flac.php

    diff --git wp-includes/ID3/module.audio.flac.php wp-includes/ID3/module.audio.flac.php
    new file mode 100644
    index 0000000..6b9598c
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio.flac.php                                       //
     11// module for analyzing FLAC and OggFLAC audio files           //
     12// dependencies: module.audio.ogg.php                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16
     17getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
     18
     19/**
     20* @tutorial http://flac.sourceforge.net/format.html
     21*/
     22class getid3_flac extends getid3_handler
     23{
     24        const syncword = 'fLaC';
     25
     26        public function Analyze() {
     27                $info = &$this->getid3->info;
     28
     29                $this->fseek($info['avdataoffset']);
     30                $StreamMarker = $this->fread(4);
     31                if ($StreamMarker != self::syncword) {
     32                        return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
     33                }
     34                $info['fileformat']            = 'flac';
     35                $info['audio']['dataformat']   = 'flac';
     36                $info['audio']['bitrate_mode'] = 'vbr';
     37                $info['audio']['lossless']     = true;
     38
     39                // parse flac container
     40                return $this->parseMETAdata();
     41        }
     42
     43        public function parseMETAdata() {
     44                $info = &$this->getid3->info;
     45                do {
     46                        $BlockOffset   = $this->ftell();
     47                        $BlockHeader   = $this->fread(4);
     48                        $LBFBT         = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));
     49                        $LastBlockFlag = (bool) ($LBFBT & 0x80);
     50                        $BlockType     =        ($LBFBT & 0x7F);
     51                        $BlockLength   = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
     52                        $BlockTypeText = self::metaBlockTypeLookup($BlockType);
     53
     54                        if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
     55                                $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
     56                                break;
     57                        }
     58                        if ($BlockLength < 1) {
     59                                $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
     60                                break;
     61                        }
     62
     63                        $info['flac'][$BlockTypeText]['raw'] = array();
     64                        $BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
     65
     66                        $BlockTypeText_raw['offset']          = $BlockOffset;
     67                        $BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
     68                        $BlockTypeText_raw['block_type']      = $BlockType;
     69                        $BlockTypeText_raw['block_type_text'] = $BlockTypeText;
     70                        $BlockTypeText_raw['block_length']    = $BlockLength;
     71                        if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
     72                                $BlockTypeText_raw['block_data']  = $this->fread($BlockLength);
     73                        }
     74
     75                        switch ($BlockTypeText) {
     76                                case 'STREAMINFO':     // 0x00
     77                                        if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
     78                                                return false;
     79                                        }
     80                                        break;
     81
     82                                case 'PADDING':        // 0x01
     83                                        unset($info['flac']['PADDING']); // ignore
     84                                        break;
     85
     86                                case 'APPLICATION':    // 0x02
     87                                        if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
     88                                                return false;
     89                                        }
     90                                        break;
     91
     92                                case 'SEEKTABLE':      // 0x03
     93                                        if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
     94                                                return false;
     95                                        }
     96                                        break;
     97
     98                                case 'VORBIS_COMMENT': // 0x04
     99                                        if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
     100                                                return false;
     101                                        }
     102                                        break;
     103
     104                                case 'CUESHEET':       // 0x05
     105                                        if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
     106                                                return false;
     107                                        }
     108                                        break;
     109
     110                                case 'PICTURE':        // 0x06
     111                                        if (!$this->parsePICTURE()) {
     112                                                return false;
     113                                        }
     114                                        break;
     115
     116                                default:
     117                                        $this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
     118                        }
     119
     120                        unset($info['flac'][$BlockTypeText]['raw']);
     121                        $info['avdataoffset'] = $this->ftell();
     122                }
     123                while ($LastBlockFlag === false);
     124
     125                // handle tags
     126                if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
     127                        $info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
     128                }
     129                if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
     130                        $info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
     131                }
     132
     133                // copy attachments to 'comments' array if nesesary
     134                if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
     135                        foreach ($info['flac']['PICTURE'] as $entry) {
     136                                if (!empty($entry['data'])) {
     137                                        $info['flac']['comments']['picture'][] = array('image_mime'=>$entry['image_mime'], 'data'=>$entry['data']);
     138                                }
     139                        }
     140                }
     141
     142                if (isset($info['flac']['STREAMINFO'])) {
     143                        if (!$this->isDependencyFor('matroska')) {
     144                                $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
     145                        }
     146                        $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
     147                        if ($info['flac']['uncompressed_audio_bytes'] == 0) {
     148                                return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
     149                        }
     150                        if (!empty($info['flac']['compressed_audio_bytes'])) {
     151                                $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
     152                        }
     153                }
     154
     155                // set md5_data_source - built into flac 0.5+
     156                if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
     157
     158                        if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
     159                $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
     160                        }
     161                        else {
     162                                $info['md5_data_source'] = '';
     163                                $md5 = $info['flac']['STREAMINFO']['audio_signature'];
     164                                for ($i = 0; $i < strlen($md5); $i++) {
     165                                        $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
     166                                }
     167                                if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
     168                                        unset($info['md5_data_source']);
     169                                }
     170                        }
     171                }
     172
     173                if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
     174                        $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
     175                        if ($info['audio']['bits_per_sample'] == 8) {
     176                                // special case
     177                                // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
     178                                // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
     179                                $this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
     180                        }
     181                }
     182
     183                return true;
     184        }
     185
     186        private function parseSTREAMINFO($BlockData) {
     187                $info = &$this->getid3->info;
     188
     189                $info['flac']['STREAMINFO'] = array();
     190                $streaminfo = &$info['flac']['STREAMINFO'];
     191
     192                $streaminfo['min_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
     193                $streaminfo['max_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
     194                $streaminfo['min_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
     195                $streaminfo['max_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
     196
     197                $SRCSBSS                       = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
     198                $streaminfo['sample_rate']     = getid3_lib::Bin2Dec(substr($SRCSBSS,  0, 20));
     199                $streaminfo['channels']        = getid3_lib::Bin2Dec(substr($SRCSBSS, 20,  3)) + 1;
     200                $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23,  5)) + 1;
     201                $streaminfo['samples_stream']  = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
     202
     203                $streaminfo['audio_signature'] = substr($BlockData, 18, 16);
     204
     205                if (!empty($streaminfo['sample_rate'])) {
     206
     207                        $info['audio']['bitrate_mode']    = 'vbr';
     208                        $info['audio']['sample_rate']     = $streaminfo['sample_rate'];
     209                        $info['audio']['channels']        = $streaminfo['channels'];
     210                        $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
     211                        $info['playtime_seconds']         = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
     212                        if ($info['playtime_seconds'] > 0) {
     213                                if (!$this->isDependencyFor('matroska')) {
     214                                        $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
     215                                }
     216                                else {
     217                                        $this->warning('Cannot determine audio bitrate because total stream size is unknown');
     218                                }
     219                        }
     220
     221                } else {
     222                        return $this->error('Corrupt METAdata block: STREAMINFO');
     223                }
     224
     225                return true;
     226        }
     227
     228        private function parseAPPLICATION($BlockData) {
     229                $info = &$this->getid3->info;
     230
     231                $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
     232                $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
     233                $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
     234
     235                return true;
     236        }
     237
     238        private function parseSEEKTABLE($BlockData) {
     239                $info = &$this->getid3->info;
     240
     241                $offset = 0;
     242                $BlockLength = strlen($BlockData);
     243                $placeholderpattern = str_repeat("\xFF", 8);
     244                while ($offset < $BlockLength) {
     245                        $SampleNumberString = substr($BlockData, $offset, 8);
     246                        $offset += 8;
     247                        if ($SampleNumberString == $placeholderpattern) {
     248
     249                                // placeholder point
     250                                getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
     251                                $offset += 10;
     252
     253                        } else {
     254
     255                                $SampleNumber                                        = getid3_lib::BigEndian2Int($SampleNumberString);
     256                                $info['flac']['SEEKTABLE'][$SampleNumber]['offset']  = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
     257                                $offset += 8;
     258                                $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
     259                                $offset += 2;
     260
     261                        }
     262                }
     263
     264                return true;
     265        }
     266
     267        private function parseVORBIS_COMMENT($BlockData) {
     268                $info = &$this->getid3->info;
     269
     270                $getid3_ogg = new getid3_ogg($this->getid3);
     271                if ($this->isDependencyFor('matroska')) {
     272                        $getid3_ogg->setStringMode($this->data_string);
     273                }
     274                $getid3_ogg->ParseVorbisComments();
     275                if (isset($info['ogg'])) {
     276                        unset($info['ogg']['comments_raw']);
     277                        $info['flac']['VORBIS_COMMENT'] = $info['ogg'];
     278                        unset($info['ogg']);
     279                }
     280
     281                unset($getid3_ogg);
     282
     283                return true;
     284        }
     285
     286        private function parseCUESHEET($BlockData) {
     287                $info = &$this->getid3->info;
     288                $offset = 0;
     289                $info['flac']['CUESHEET']['media_catalog_number'] =                              trim(substr($BlockData, $offset, 128), "\0");
     290                $offset += 128;
     291                $info['flac']['CUESHEET']['lead_in_samples']      =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
     292                $offset += 8;
     293                $info['flac']['CUESHEET']['flags']['is_cd']       = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
     294                $offset += 1;
     295
     296                $offset += 258; // reserved
     297
     298                $info['flac']['CUESHEET']['number_tracks']        =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
     299                $offset += 1;
     300
     301                for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
     302                        $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
     303                        $offset += 8;
     304                        $TrackNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
     305                        $offset += 1;
     306
     307                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset']         = $TrackSampleOffset;
     308
     309                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc']                  =                           substr($BlockData, $offset, 12);
     310                        $offset += 12;
     311
     312                        $TrackFlagsRaw                                                             = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
     313                        $offset += 1;
     314                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio']     = (bool) ($TrackFlagsRaw & 0x80);
     315                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
     316
     317                        $offset += 13; // reserved
     318
     319                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']          = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
     320                        $offset += 1;
     321
     322                        for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
     323                                $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
     324                                $offset += 8;
     325                                $IndexNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
     326                                $offset += 1;
     327
     328                                $offset += 3; // reserved
     329
     330                                $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
     331                        }
     332                }
     333
     334                return true;
     335        }
     336
     337        /**
     338        * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
     339        * External usage: audio.ogg
     340        */
     341        public function parsePICTURE() {
     342                $info = &$this->getid3->info;
     343
     344                $picture['typeid']         = getid3_lib::BigEndian2Int($this->fread(4));
     345                $picture['type']           = self::pictureTypeLookup($picture['typeid']);
     346                $picture['image_mime']     = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
     347                $descr_length              = getid3_lib::BigEndian2Int($this->fread(4));
     348                if ($descr_length) {
     349                        $picture['description'] = $this->fread($descr_length);
     350                }
     351                $picture['width']          = getid3_lib::BigEndian2Int($this->fread(4));
     352                $picture['height']         = getid3_lib::BigEndian2Int($this->fread(4));
     353                $picture['color_depth']    = getid3_lib::BigEndian2Int($this->fread(4));
     354                $picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
     355                $data_length               = getid3_lib::BigEndian2Int($this->fread(4));
     356
     357                if ($picture['image_mime'] == '-->') {
     358                        $picture['data'] = $this->fread($data_length);
     359                } else {
     360                        $picture['data'] = $this->saveAttachment(
     361                                str_replace('/', '_', $picture['type']).'_'.$this->ftell(),
     362                                $this->ftell(),
     363                                $data_length,
     364                                $picture['image_mime']);
     365                }
     366
     367                $info['flac']['PICTURE'][] = $picture;
     368
     369                return true;
     370        }
     371
     372        public static function metaBlockTypeLookup($blocktype) {
     373                static $lookup = array(
     374                        0 => 'STREAMINFO',
     375                        1 => 'PADDING',
     376                        2 => 'APPLICATION',
     377                        3 => 'SEEKTABLE',
     378                        4 => 'VORBIS_COMMENT',
     379                        5 => 'CUESHEET',
     380                        6 => 'PICTURE',
     381                );
     382                return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
     383        }
     384
     385        public static function applicationIDLookup($applicationid) {
     386                // http://flac.sourceforge.net/id.html
     387                static $lookup = array(
     388                        0x41544348 => 'FlacFile',                                                                           // "ATCH"
     389                        0x42534F4C => 'beSolo',                                                                             // "BSOL"
     390                        0x42554753 => 'Bugs Player',                                                                        // "BUGS"
     391                        0x43756573 => 'GoldWave cue points (specification)',                                                // "Cues"
     392                        0x46696361 => 'CUE Splitter',                                                                       // "Fica"
     393                        0x46746F6C => 'flac-tools',                                                                         // "Ftol"
     394                        0x4D4F5442 => 'MOTB MetaCzar',                                                                      // "MOTB"
     395                        0x4D505345 => 'MP3 Stream Editor',                                                                  // "MPSE"
     396                        0x4D754D4C => 'MusicML: Music Metadata Language',                                                   // "MuML"
     397                        0x52494646 => 'Sound Devices RIFF chunk storage',                                                   // "RIFF"
     398                        0x5346464C => 'Sound Font FLAC',                                                                    // "SFFL"
     399                        0x534F4E59 => 'Sony Creative Software',                                                             // "SONY"
     400                        0x5351455A => 'flacsqueeze',                                                                        // "SQEZ"
     401                        0x54745776 => 'TwistedWave',                                                                        // "TtWv"
     402                        0x55495453 => 'UITS Embedding tools',                                                               // "UITS"
     403                        0x61696666 => 'FLAC AIFF chunk storage',                                                            // "aiff"
     404                        0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks',  // "imag"
     405                        0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',                             // "peem"
     406                        0x71667374 => 'QFLAC Studio',                                                                       // "qfst"
     407                        0x72696666 => 'FLAC RIFF chunk storage',                                                            // "riff"
     408                        0x74756E65 => 'TagTuner',                                                                           // "tune"
     409                        0x78626174 => 'XBAT',                                                                               // "xbat"
     410                        0x786D6364 => 'xmcd',                                                                               // "xmcd"
     411                );
     412                return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
     413        }
     414
     415        public static function pictureTypeLookup($type_id) {
     416                static $lookup = array (
     417                         0 => 'Other',
     418                         1 => '32x32 pixels \'file icon\' (PNG only)',
     419                         2 => 'Other file icon',
     420                         3 => 'Cover (front)',
     421                         4 => 'Cover (back)',
     422                         5 => 'Leaflet page',
     423                         6 => 'Media (e.g. label side of CD)',
     424                         7 => 'Lead artist/lead performer/soloist',
     425                         8 => 'Artist/performer',
     426                         9 => 'Conductor',
     427                        10 => 'Band/Orchestra',
     428                        11 => 'Composer',
     429                        12 => 'Lyricist/text writer',
     430                        13 => 'Recording Location',
     431                        14 => 'During recording',
     432                        15 => 'During performance',
     433                        16 => 'Movie/video screen capture',
     434                        17 => 'A bright coloured fish',
     435                        18 => 'Illustration',
     436                        19 => 'Band/artist logotype',
     437                        20 => 'Publisher/Studio logotype',
     438                );
     439                return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
     440        }
     441
     442}
  • new file wp-includes/ID3/module.audio.mp3.php

    diff --git wp-includes/ID3/module.audio.mp3.php wp-includes/ID3/module.audio.mp3.php
    new file mode 100644
    index 0000000..e6ffea9
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio.mp3.php                                        //
     11// module for analyzing MP3 files                              //
     12// dependencies: NONE                                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16
     17// number of frames to scan to determine if MPEG-audio sequence is valid
     18// Lower this number to 5-20 for faster scanning
     19// Increase this number to 50+ for most accurate detection of valid VBR/CBR
     20// mpeg-audio streams
     21define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
     22
     23
     24class getid3_mp3 extends getid3_handler
     25{
     26
     27        public $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files
     28
     29        public function Analyze() {
     30                $info = &$this->getid3->info;
     31
     32                $initialOffset = $info['avdataoffset'];
     33
     34                if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
     35                        if ($this->allow_bruteforce) {
     36                                $info['error'][] = 'Rescanning file in BruteForce mode';
     37                                $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info);
     38                        }
     39                }
     40
     41
     42                if (isset($info['mpeg']['audio']['bitrate_mode'])) {
     43                        $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
     44                }
     45
     46                if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
     47
     48                        $synchoffsetwarning = 'Unknown data before synch ';
     49                        if (isset($info['id3v2']['headerlength'])) {
     50                                $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, ';
     51                        } elseif ($initialOffset > 0) {
     52                                $synchoffsetwarning .= '(should be at '.$initialOffset.', ';
     53                        } else {
     54                                $synchoffsetwarning .= '(should be at beginning of file, ';
     55                        }
     56                        $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')';
     57                        if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) {
     58
     59                                if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) {
     60
     61                                        $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.';
     62                                        $info['audio']['codec'] = 'LAME';
     63                                        $CurrentDataLAMEversionString = 'LAME3.';
     64
     65                                } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) {
     66
     67                                        $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.';
     68                                        $info['audio']['codec'] = 'LAME';
     69                                        $CurrentDataLAMEversionString = 'LAME3.';
     70
     71                                }
     72
     73                        }
     74                        $info['warning'][] = $synchoffsetwarning;
     75
     76                }
     77
     78                if (isset($info['mpeg']['audio']['LAME'])) {
     79                        $info['audio']['codec'] = 'LAME';
     80                        if (!empty($info['mpeg']['audio']['LAME']['long_version'])) {
     81                                $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00");
     82                        } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) {
     83                                $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00");
     84                        }
     85                }
     86
     87                $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : ''));
     88                if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) {
     89                        // a version number of LAME that does not end with a number like "LAME3.92"
     90                        // or with a closing parenthesis like "LAME3.88 (alpha)"
     91                        // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92)
     92
     93                        // not sure what the actual last frame length will be, but will be less than or equal to 1441
     94                        $PossiblyLongerLAMEversion_FrameLength = 1441;
     95
     96                        // Not sure what version of LAME this is - look in padding of last frame for longer version string
     97                        $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength;
     98                        fseek($this->getid3->fp, $PossibleLAMEversionStringOffset);
     99                        $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength);
     100                        switch (substr($CurrentDataLAMEversionString, -1)) {
     101                                case 'a':
     102                                case 'b':
     103                                        // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example
     104                                        // need to trim off "a" to match longer string
     105                                        $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1);
     106                                        break;
     107                        }
     108                        if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) {
     109                                if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
     110                                        $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3"  "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
     111                                        if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
     112                                                $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
     113                                        }
     114                                }
     115                        }
     116                }
     117                if (!empty($info['audio']['encoder'])) {
     118                        $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 ");
     119                }
     120
     121                switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') {
     122                        case 1:
     123                        case 2:
     124                                $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer'];
     125                                break;
     126                }
     127                if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) {
     128                        switch ($info['audio']['dataformat']) {
     129                                case 'mp1':
     130                                case 'mp2':
     131                                case 'mp3':
     132                                        $info['fileformat'] = $info['audio']['dataformat'];
     133                                        break;
     134
     135                                default:
     136                                        $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"';
     137                                        break;
     138                        }
     139                }
     140
     141                if (empty($info['fileformat'])) {
     142                        unset($info['fileformat']);
     143                        unset($info['audio']['bitrate_mode']);
     144                        unset($info['avdataoffset']);
     145                        unset($info['avdataend']);
     146                        return false;
     147                }
     148
     149                $info['mime_type']         = 'audio/mpeg';
     150                $info['audio']['lossless'] = false;
     151
     152                // Calculate playtime
     153                if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
     154                        $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate'];
     155                }
     156
     157                $info['audio']['encoder_options'] = $this->GuessEncoderOptions();
     158
     159                return true;
     160        }
     161
     162
     163        public function GuessEncoderOptions() {
     164                // shortcuts
     165                $info = &$this->getid3->info;
     166                if (!empty($info['mpeg']['audio'])) {
     167                        $thisfile_mpeg_audio = &$info['mpeg']['audio'];
     168                        if (!empty($thisfile_mpeg_audio['LAME'])) {
     169                                $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
     170                        }
     171                }
     172
     173                $encoder_options = '';
     174                static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256);
     175
     176                if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) {
     177
     178                        $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];
     179
     180                } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
     181
     182                        $encoder_options = $thisfile_mpeg_audio_lame['preset_used'];
     183
     184                } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) {
     185
     186                        static $KnownEncoderValues = array();
     187                        if (empty($KnownEncoderValues)) {
     188
     189                                //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name';
     190                                $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane';        // 3.90,   3.90.1, 3.92
     191                                $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane';        // 3.90.2, 3.90.3, 3.91
     192                                $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane';        // 3.94,   3.95
     193                                $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme';       // 3.90,   3.90.1, 3.92
     194                                $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme';       // 3.90.2, 3.91
     195                                $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme';       // 3.90.3
     196                                $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme';  // 3.90,   3.90.1, 3.92
     197                                $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme';  // 3.90.2, 3.90.3, 3.91
     198                                $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard';      // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
     199                                $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard';      // 3.90.3
     200                                $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
     201                                $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3
     202                                $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix';                    // 3.90,   3.90.1, 3.92
     203                                $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix';                    // 3.90.2, 3.90.3, 3.91
     204                                $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix';                    // 3.94,   3.95
     205                                $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium';        // 3.90.3
     206                                $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium';   // 3.90.3
     207
     208                                $KnownEncoderValues[0xFF][99][1][1][1][2][0]     = '--preset studio';            // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
     209                                $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio';            // 3.90.3, 3.93.1
     210                                $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio';            // 3.93
     211                                $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio';            // 3.94,   3.95
     212                                $KnownEncoderValues[0xC0][88][1][1][1][2][0]     = '--preset cd';                // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
     213                                $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd';                // 3.90.3, 3.93.1
     214                                $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd';                // 3.93
     215                                $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd';                // 3.94,   3.95
     216                                $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi';              // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
     217                                $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi';              // 3.90.3, 3.93,   3.93.1
     218                                $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi';              // 3.94,   3.95
     219                                $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape';              // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
     220                                $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio';             // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
     221                                $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm';                // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
     222                                $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm';     // 3.90.3, 3.93,   3.93.1
     223                                $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm';     // 3.94,   3.95
     224                                $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice';             // 3.90.3, 3.93,   3.93.1
     225                                $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice';             // 3.94,   3.95
     226                                $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice';             // 3.94a14
     227                                $KnownEncoderValues[0x28][65][1][1][0][2][7500]  = '--preset mw-us';             // 3.90,   3.90.1, 3.92
     228                                $KnownEncoderValues[0x28][65][1][1][0][2][7600]  = '--preset mw-us';             // 3.90.2, 3.91
     229                                $KnownEncoderValues[0x28][58][2][2][0][2][7000]  = '--preset mw-us';             // 3.90.3, 3.93,   3.93.1
     230                                $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us';             // 3.94,   3.95
     231                                $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us';             // 3.94a14
     232                                $KnownEncoderValues[0x28][57][2][1][0][4][8800]  = '--preset mw-us';             // 3.94a15
     233                                $KnownEncoderValues[0x18][58][2][2][0][2][4000]  = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1
     234                                $KnownEncoderValues[0x18][58][2][2][0][2][3900]  = '--preset phon+/lw/mw-eu/sw'; // 3.93
     235                                $KnownEncoderValues[0x18][57][2][1][0][4][5900]  = '--preset phon+/lw/mw-eu/sw'; // 3.94,   3.95
     236                                $KnownEncoderValues[0x18][57][2][1][0][4][6200]  = '--preset phon+/lw/mw-eu/sw'; // 3.94a14
     237                                $KnownEncoderValues[0x18][57][2][1][0][4][3200]  = '--preset phon+/lw/mw-eu/sw'; // 3.94a15
     238                                $KnownEncoderValues[0x10][58][2][2][0][2][3800]  = '--preset phone';             // 3.90.3, 3.93.1
     239                                $KnownEncoderValues[0x10][58][2][2][0][2][3700]  = '--preset phone';             // 3.93
     240                                $KnownEncoderValues[0x10][57][2][1][0][4][5600]  = '--preset phone';             // 3.94,   3.95
     241                        }
     242
     243                        if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
     244
     245                                $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
     246
     247                        } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
     248
     249                                $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
     250
     251                        } elseif ($info['audio']['bitrate_mode'] == 'vbr') {
     252
     253                                // http://gabriel.mp3-tech.org/mp3infotag.html
     254                                // int    Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h
     255
     256
     257                                $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10);
     258                                $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10);
     259                                $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value;
     260
     261                        } elseif ($info['audio']['bitrate_mode'] == 'cbr') {
     262
     263                                $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
     264
     265                        } else {
     266
     267                                $encoder_options = strtoupper($info['audio']['bitrate_mode']);
     268
     269                        }
     270
     271                } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) {
     272
     273                        $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr'];
     274
     275                } elseif (!empty($info['audio']['bitrate'])) {
     276
     277                        if ($info['audio']['bitrate_mode'] == 'cbr') {
     278                                $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
     279                        } else {
     280                                $encoder_options = strtoupper($info['audio']['bitrate_mode']);
     281                        }
     282
     283                }
     284                if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) {
     285                        $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min'];
     286                }
     287
     288                if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) {
     289                        $encoder_options .= ' --nogap';
     290                }
     291
     292                if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) {
     293                        $ExplodedOptions = explode(' ', $encoder_options, 4);
     294                        if ($ExplodedOptions[0] == '--r3mix') {
     295                                $ExplodedOptions[1] = 'r3mix';
     296                        }
     297                        switch ($ExplodedOptions[0]) {
     298                                case '--preset':
     299                                case '--alt-preset':
     300                                case '--r3mix':
     301                                        if ($ExplodedOptions[1] == 'fast') {
     302                                                $ExplodedOptions[1] .= ' '.$ExplodedOptions[2];
     303                                        }
     304                                        switch ($ExplodedOptions[1]) {
     305                                                case 'portable':
     306                                                case 'medium':
     307                                                case 'standard':
     308                                                case 'extreme':
     309                                                case 'insane':
     310                                                case 'fast portable':
     311                                                case 'fast medium':
     312                                                case 'fast standard':
     313                                                case 'fast extreme':
     314                                                case 'fast insane':
     315                                                case 'r3mix':
     316                                                        static $ExpectedLowpass = array(
     317                                                                        'insane|20500'        => 20500,
     318                                                                        'insane|20600'        => 20600,  // 3.90.2, 3.90.3, 3.91
     319                                                                        'medium|18000'        => 18000,
     320                                                                        'fast medium|18000'   => 18000,
     321                                                                        'extreme|19500'       => 19500,  // 3.90,   3.90.1, 3.92, 3.95
     322                                                                        'extreme|19600'       => 19600,  // 3.90.2, 3.90.3, 3.91, 3.93.1
     323                                                                        'fast extreme|19500'  => 19500,  // 3.90,   3.90.1, 3.92, 3.95
     324                                                                        'fast extreme|19600'  => 19600,  // 3.90.2, 3.90.3, 3.91, 3.93.1
     325                                                                        'standard|19000'      => 19000,
     326                                                                        'fast standard|19000' => 19000,
     327                                                                        'r3mix|19500'         => 19500,  // 3.90,   3.90.1, 3.92
     328                                                                        'r3mix|19600'         => 19600,  // 3.90.2, 3.90.3, 3.91
     329                                                                        'r3mix|18000'         => 18000,  // 3.94,   3.95
     330                                                                );
     331                                                        if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) {
     332                                                                $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency'];
     333                                                        }
     334                                                        break;
     335
     336                                                default:
     337                                                        break;
     338                                        }
     339                                        break;
     340                        }
     341                }
     342
     343                if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) {
     344                        if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) {
     345                                $encoder_options .= ' --resample 44100';
     346                        } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) {
     347                                $encoder_options .= ' --resample 48000';
     348                        } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) {
     349                                switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) {
     350                                        case 0: // <= 32000
     351                                                // may or may not be same as source frequency - ignore
     352                                                break;
     353                                        case 1: // 44100
     354                                        case 2: // 48000
     355                                        case 3: // 48000+
     356                                                $ExplodedOptions = explode(' ', $encoder_options, 4);
     357                                                switch ($ExplodedOptions[0]) {
     358                                                        case '--preset':
     359                                                        case '--alt-preset':
     360                                                                switch ($ExplodedOptions[1]) {
     361                                                                        case 'fast':
     362                                                                        case 'portable':
     363                                                                        case 'medium':
     364                                                                        case 'standard':
     365                                                                        case 'extreme':
     366                                                                        case 'insane':
     367                                                                                $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
     368                                                                                break;
     369
     370                                                                        default:
     371                                                                                static $ExpectedResampledRate = array(
     372                                                                                                'phon+/lw/mw-eu/sw|16000' => 16000,
     373                                                                                                'mw-us|24000'             => 24000, // 3.95
     374                                                                                                'mw-us|32000'             => 32000, // 3.93
     375                                                                                                'mw-us|16000'             => 16000, // 3.92
     376                                                                                                'phone|16000'             => 16000,
     377                                                                                                'phone|11025'             => 11025, // 3.94a15
     378                                                                                                'radio|32000'             => 32000, // 3.94a15
     379                                                                                                'fm/radio|32000'          => 32000, // 3.92
     380                                                                                                'fm|32000'                => 32000, // 3.90
     381                                                                                                'voice|32000'             => 32000);
     382                                                                                if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) {
     383                                                                                        $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
     384                                                                                }
     385                                                                                break;
     386                                                                }
     387                                                                break;
     388
     389                                                        case '--r3mix':
     390                                                        default:
     391                                                                $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
     392                                                                break;
     393                                                }
     394                                                break;
     395                                }
     396                        }
     397                }
     398                if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) {
     399                        //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
     400                        $encoder_options = strtoupper($info['audio']['bitrate_mode']);
     401                }
     402
     403                return $encoder_options;
     404        }
     405
     406
     407        public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
     408                static $MPEGaudioVersionLookup;
     409                static $MPEGaudioLayerLookup;
     410                static $MPEGaudioBitrateLookup;
     411                static $MPEGaudioFrequencyLookup;
     412                static $MPEGaudioChannelModeLookup;
     413                static $MPEGaudioModeExtensionLookup;
     414                static $MPEGaudioEmphasisLookup;
     415                if (empty($MPEGaudioVersionLookup)) {
     416                        $MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
     417                        $MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
     418                        $MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
     419                        $MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
     420                        $MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
     421                        $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
     422                        $MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
     423                }
     424
     425                if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) {
     426                        $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset;
     427                        return false;
     428                }
     429                //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
     430                $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data
     431
     432                // MP3 audio frame structure:
     433                // $aa $aa $aa $aa [$bb $bb] $cc...
     434                // where $aa..$aa is the four-byte mpeg-audio header (below)
     435                // $bb $bb is the optional 2-byte CRC
     436                // and $cc... is the audio data
     437
     438                $head4 = substr($headerstring, 0, 4);
     439
     440                static $MPEGaudioHeaderDecodeCache = array();
     441                if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
     442                        $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
     443                } else {
     444                        $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
     445                        $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
     446                }
     447
     448                static $MPEGaudioHeaderValidCache = array();
     449                if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache
     450                        //$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
     451                        $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
     452                }
     453
     454                // shortcut
     455                if (!isset($info['mpeg']['audio'])) {
     456                        $info['mpeg']['audio'] = array();
     457                }
     458                $thisfile_mpeg_audio = &$info['mpeg']['audio'];
     459
     460
     461                if ($MPEGaudioHeaderValidCache[$head4]) {
     462                        $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
     463                } else {
     464                        $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset;
     465                        return false;
     466                }
     467
     468                if (!$FastMPEGheaderScan) {
     469                        $thisfile_mpeg_audio['version']       = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']];
     470                        $thisfile_mpeg_audio['layer']         = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']];
     471
     472                        $thisfile_mpeg_audio['channelmode']   = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']];
     473                        $thisfile_mpeg_audio['channels']      = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2);
     474                        $thisfile_mpeg_audio['sample_rate']   = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']];
     475                        $thisfile_mpeg_audio['protection']    = !$thisfile_mpeg_audio['raw']['protection'];
     476                        $thisfile_mpeg_audio['private']       = (bool) $thisfile_mpeg_audio['raw']['private'];
     477                        $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']];
     478                        $thisfile_mpeg_audio['copyright']     = (bool) $thisfile_mpeg_audio['raw']['copyright'];
     479                        $thisfile_mpeg_audio['original']      = (bool) $thisfile_mpeg_audio['raw']['original'];
     480                        $thisfile_mpeg_audio['emphasis']      = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']];
     481
     482                        $info['audio']['channels']    = $thisfile_mpeg_audio['channels'];
     483                        $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate'];
     484
     485                        if ($thisfile_mpeg_audio['protection']) {
     486                                $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2));
     487                        }
     488                }
     489
     490                if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
     491                        // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
     492                        $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1';
     493                        $thisfile_mpeg_audio['raw']['bitrate'] = 0;
     494                }
     495                $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
     496                $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']];
     497
     498                if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) {
     499                        // only skip multiple frame check if free-format bitstream found at beginning of file
     500                        // otherwise is quite possibly simply corrupted data
     501                        $recursivesearch = false;
     502                }
     503
     504                // For Layer 2 there are some combinations of bitrate and mode which are not allowed.
     505                if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) {
     506
     507                        $info['audio']['dataformat'] = 'mp2';
     508                        switch ($thisfile_mpeg_audio['channelmode']) {
     509
     510                                case 'mono':
     511                                        if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
     512                                                // these are ok
     513                                        } else {
     514                                                $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
     515                                                return false;
     516                                        }
     517                                        break;
     518
     519                                case 'stereo':
     520                                case 'joint stereo':
     521                                case 'dual channel':
     522                                        if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
     523                                                // these are ok
     524                                        } else {
     525                                                $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
     526                                                return false;
     527                                        }
     528                                        break;
     529
     530                        }
     531
     532                }
     533
     534
     535                if ($info['audio']['sample_rate'] > 0) {
     536                        $thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']);
     537                }
     538
     539                $nextframetestoffset = $offset + 1;
     540                if ($thisfile_mpeg_audio['bitrate'] != 'free') {
     541
     542                        $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
     543
     544                        if (isset($thisfile_mpeg_audio['framelength'])) {
     545                                $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
     546                        } else {
     547                                $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.';
     548                                return false;
     549                        }
     550
     551                }
     552
     553                $ExpectedNumberOfAudioBytes = 0;
     554
     555                ////////////////////////////////////////////////////////////////////////////////////
     556                // Variable-bitrate headers
     557
     558                if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
     559                        // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
     560                        // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
     561
     562                        $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
     563                        $thisfile_mpeg_audio['VBR_method']   = 'Fraunhofer';
     564                        $info['audio']['codec']                = 'Fraunhofer';
     565
     566                        $SideInfoData = substr($headerstring, 4 + 2, 32);
     567
     568                        $FraunhoferVBROffset = 36;
     569
     570                        $thisfile_mpeg_audio['VBR_encoder_version']     = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  4, 2)); // VbriVersion
     571                        $thisfile_mpeg_audio['VBR_encoder_delay']       = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  6, 2)); // VbriDelay
     572                        $thisfile_mpeg_audio['VBR_quality']             = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  8, 2)); // VbriQuality
     573                        $thisfile_mpeg_audio['VBR_bytes']               = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes
     574                        $thisfile_mpeg_audio['VBR_frames']              = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames
     575                        $thisfile_mpeg_audio['VBR_seek_offsets']        = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize
     576                        $thisfile_mpeg_audio['VBR_seek_scale']          = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale
     577                        $thisfile_mpeg_audio['VBR_entry_bytes']         = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes
     578                        $thisfile_mpeg_audio['VBR_entry_frames']        = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames
     579
     580                        $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes'];
     581
     582                        $previousbyteoffset = $offset;
     583                        for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) {
     584                                $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes']));
     585                                $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes'];
     586                                $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']);
     587                                $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset;
     588                                $previousbyteoffset += $Fraunhofer_OffsetN;
     589                        }
     590
     591
     592                } else {
     593
     594                        // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
     595                        // depending on MPEG layer and number of channels
     596
     597                        $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']);
     598                        $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4);
     599
     600                        if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
     601                                // 'Xing' is traditional Xing VBR frame
     602                                // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
     603                                // 'Info' *can* legally be used to specify a VBR file as well, however.
     604
     605                                // http://www.multiweb.cz/twoinches/MP3inside.htm
     606                                //00..03 = "Xing" or "Info"
     607                                //04..07 = Flags:
     608                                //  0x01  Frames Flag     set if value for number of frames in file is stored
     609                                //  0x02  Bytes Flag      set if value for filesize in bytes is stored
     610                                //  0x04  TOC Flag        set if values for TOC are stored
     611                                //  0x08  VBR Scale Flag  set if values for VBR scale is stored
     612                                //08..11  Frames: Number of frames in file (including the first Xing/Info one)
     613                                //12..15  Bytes:  File length in Bytes
     614                                //16..115  TOC (Table of Contents):
     615                                //  Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file.
     616                                //  Each Byte has a value according this formula:
     617                                //  (TOC[i] / 256) * fileLenInBytes
     618                                //  So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use:
     619                                //  TOC[(60/240)*100] = TOC[25]
     620                                //  and corresponding Byte in file is then approximately at:
     621                                //  (TOC[25]/256) * 5000000
     622                                //116..119  VBR Scale
     623
     624
     625                                // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME
     626//                              if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') {
     627                                        $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
     628                                        $thisfile_mpeg_audio['VBR_method']   = 'Xing';
     629//                              } else {
     630//                                      $ScanAsCBR = true;
     631//                                      $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
     632//                              }
     633
     634                                $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
     635
     636                                $thisfile_mpeg_audio['xing_flags']['frames']    = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001);
     637                                $thisfile_mpeg_audio['xing_flags']['bytes']     = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002);
     638                                $thisfile_mpeg_audio['xing_flags']['toc']       = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004);
     639                                $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008);
     640
     641                                if ($thisfile_mpeg_audio['xing_flags']['frames']) {
     642                                        $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset +  8, 4));
     643                                        //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame
     644                                }
     645                                if ($thisfile_mpeg_audio['xing_flags']['bytes']) {
     646                                        $thisfile_mpeg_audio['VBR_bytes']  = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
     647                                }
     648
     649                                //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
     650                                if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
     651
     652                                        $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames'];
     653
     654                                        if ($thisfile_mpeg_audio['layer'] == '1') {
     655                                                // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
     656                                                //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
     657                                                $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12;
     658                                        } else {
     659                                                // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
     660                                                //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
     661                                                $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144;
     662                                        }
     663                                        $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat);
     664                                }
     665
     666                                if ($thisfile_mpeg_audio['xing_flags']['toc']) {
     667                                        $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
     668                                        for ($i = 0; $i < 100; $i++) {
     669                                                $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i});
     670                                        }
     671                                }
     672                                if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) {
     673                                        $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
     674                                }
     675
     676
     677                                // http://gabriel.mp3-tech.org/mp3infotag.html
     678                                if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
     679
     680                                        // shortcut
     681                                        $thisfile_mpeg_audio['LAME'] = array();
     682                                        $thisfile_mpeg_audio_lame    = &$thisfile_mpeg_audio['LAME'];
     683
     684
     685                                        $thisfile_mpeg_audio_lame['long_version']  = substr($headerstring, $VBRidOffset + 120, 20);
     686                                        $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
     687
     688                                        if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
     689
     690                                                // extra 11 chars are not part of version string when LAMEtag present
     691                                                unset($thisfile_mpeg_audio_lame['long_version']);
     692
     693                                                // It the LAME tag was only introduced in LAME v3.90
     694                                                // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
     695
     696                                                // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
     697                                                // are assuming a 'Xing' identifier offset of 0x24, which is the case for
     698                                                // MPEG-1 non-mono, but not for other combinations
     699                                                $LAMEtagOffsetContant = $VBRidOffset - 0x24;
     700
     701                                                // shortcuts
     702                                                $thisfile_mpeg_audio_lame['RGAD']    = array('track'=>array(), 'album'=>array());
     703                                                $thisfile_mpeg_audio_lame_RGAD       = &$thisfile_mpeg_audio_lame['RGAD'];
     704                                                $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
     705                                                $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
     706                                                $thisfile_mpeg_audio_lame['raw'] = array();
     707                                                $thisfile_mpeg_audio_lame_raw    = &$thisfile_mpeg_audio_lame['raw'];
     708
     709                                                // byte $9B  VBR Quality
     710                                                // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
     711                                                // Actually overwrites original Xing bytes
     712                                                unset($thisfile_mpeg_audio['VBR_scale']);
     713                                                $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
     714
     715                                                // bytes $9C-$A4  Encoder short VersionString
     716                                                $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
     717
     718                                                // byte $A5  Info Tag revision + VBR method
     719                                                $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
     720
     721                                                $thisfile_mpeg_audio_lame['tag_revision']   = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
     722                                                $thisfile_mpeg_audio_lame_raw['vbr_method'] =  $LAMEtagRevisionVBRmethod & 0x0F;
     723                                                $thisfile_mpeg_audio_lame['vbr_method']     = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
     724                                                $thisfile_mpeg_audio['bitrate_mode']        = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
     725
     726                                                // byte $A6  Lowpass filter value
     727                                                $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
     728
     729                                                // bytes $A7-$AE  Replay Gain
     730                                                // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
     731                                                // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
     732                                                if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
     733                                                        // LAME 3.94a16 and later - 9.23 fixed point
     734                                                        // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
     735                                                        $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
     736                                                } else {
     737                                                        // LAME 3.94a15 and earlier - 32-bit floating point
     738                                                        // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
     739                                                        $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
     740                                                }
     741                                                if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
     742                                                        unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
     743                                                } else {
     744                                                        $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
     745                                                }
     746
     747                                                $thisfile_mpeg_audio_lame_raw['RGAD_track']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
     748                                                $thisfile_mpeg_audio_lame_raw['RGAD_album']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
     749
     750
     751                                                if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
     752
     753                                                        $thisfile_mpeg_audio_lame_RGAD_track['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
     754                                                        $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
     755                                                        $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
     756                                                        $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] =  $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
     757                                                        $thisfile_mpeg_audio_lame_RGAD_track['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
     758                                                        $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
     759                                                        $thisfile_mpeg_audio_lame_RGAD_track['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
     760
     761                                                        if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
     762                                                                $info['replay_gain']['track']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
     763                                                        }
     764                                                        $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
     765                                                        $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
     766                                                } else {
     767                                                        unset($thisfile_mpeg_audio_lame_RGAD['track']);
     768                                                }
     769                                                if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
     770
     771                                                        $thisfile_mpeg_audio_lame_RGAD_album['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
     772                                                        $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
     773                                                        $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
     774                                                        $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
     775                                                        $thisfile_mpeg_audio_lame_RGAD_album['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
     776                                                        $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
     777                                                        $thisfile_mpeg_audio_lame_RGAD_album['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
     778
     779                                                        if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
     780                                                                $info['replay_gain']['album']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
     781                                                        }
     782                                                        $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
     783                                                        $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
     784                                                } else {
     785                                                        unset($thisfile_mpeg_audio_lame_RGAD['album']);
     786                                                }
     787                                                if (empty($thisfile_mpeg_audio_lame_RGAD)) {
     788                                                        unset($thisfile_mpeg_audio_lame['RGAD']);
     789                                                }
     790
     791
     792                                                // byte $AF  Encoding flags + ATH Type
     793                                                $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
     794                                                $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune']   = (bool) ($EncodingFlagsATHtype & 0x10);
     795                                                $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
     796                                                $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']  = (bool) ($EncodingFlagsATHtype & 0x40);
     797                                                $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']  = (bool) ($EncodingFlagsATHtype & 0x80);
     798                                                $thisfile_mpeg_audio_lame['ath_type']                      =         $EncodingFlagsATHtype & 0x0F;
     799
     800                                                // byte $B0  if ABR {specified bitrate} else {minimal bitrate}
     801                                                $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
     802                                                if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
     803                                                        $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
     804                                                } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
     805                                                        // ignore
     806                                                } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
     807                                                        $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
     808                                                }
     809
     810                                                // bytes $B1-$B3  Encoder delays
     811                                                $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
     812                                                $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
     813                                                $thisfile_mpeg_audio_lame['end_padding']   =  $EncoderDelays & 0x000FFF;
     814
     815                                                // byte $B4  Misc
     816                                                $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
     817                                                $thisfile_mpeg_audio_lame_raw['noise_shaping']       = ($MiscByte & 0x03);
     818                                                $thisfile_mpeg_audio_lame_raw['stereo_mode']         = ($MiscByte & 0x1C) >> 2;
     819                                                $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
     820                                                $thisfile_mpeg_audio_lame_raw['source_sample_freq']  = ($MiscByte & 0xC0) >> 6;
     821                                                $thisfile_mpeg_audio_lame['noise_shaping']       = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
     822                                                $thisfile_mpeg_audio_lame['stereo_mode']         = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
     823                                                $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
     824                                                $thisfile_mpeg_audio_lame['source_sample_freq']  = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
     825
     826                                                // byte $B5  MP3 Gain
     827                                                $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
     828                                                $thisfile_mpeg_audio_lame['mp3_gain_db']     = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
     829                                                $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
     830
     831                                                // bytes $B6-$B7  Preset and surround info
     832                                                $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
     833                                                // Reserved                                                    = ($PresetSurroundBytes & 0xC000);
     834                                                $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
     835                                                $thisfile_mpeg_audio_lame['surround_info']     = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
     836                                                $thisfile_mpeg_audio_lame['preset_used_id']    = ($PresetSurroundBytes & 0x07FF);
     837                                                $thisfile_mpeg_audio_lame['preset_used']       = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
     838                                                if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
     839                                                        $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org';
     840                                                }
     841                                                if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
     842                                                        // this may change if 3.90.4 ever comes out
     843                                                        $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
     844                                                }
     845
     846                                                // bytes $B8-$BB  MusicLength
     847                                                $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
     848                                                $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
     849
     850                                                // bytes $BC-$BD  MusicCRC
     851                                                $thisfile_mpeg_audio_lame['music_crc']    = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
     852
     853                                                // bytes $BE-$BF  CRC-16 of Info Tag
     854                                                $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
     855
     856
     857                                                // LAME CBR
     858                                                if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
     859
     860                                                        $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
     861                                                        $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
     862                                                        $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
     863                                                        //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
     864                                                        //      $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
     865                                                        //}
     866
     867                                                }
     868
     869                                        }
     870                                }
     871
     872                        } else {
     873
     874                                // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
     875                                $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
     876                                if ($recursivesearch) {
     877                                        $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
     878                                        if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) {
     879                                                $recursivesearch = false;
     880                                                $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
     881                                        }
     882                                        if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
     883                                                $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.';
     884                                        }
     885                                }
     886
     887                        }
     888
     889                }
     890
     891                if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) {
     892                        if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) {
     893                                if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) {
     894                                        // ignore, audio data is broken into chunks so will always be data "missing"
     895                                } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) {
     896                                        $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)';
     897                                } else {
     898                                        $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)';
     899                                }
     900                        } else {
     901                                if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
     902                                //      $prenullbytefileoffset = ftell($this->getid3->fp);
     903                                //      fseek($this->getid3->fp, $info['avdataend'], SEEK_SET);
     904                                //      $PossibleNullByte = fread($this->getid3->fp, 1);
     905                                //      fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET);
     906                                //      if ($PossibleNullByte === "\x00") {
     907                                                $info['avdataend']--;
     908                                //              $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored';
     909                                //      } else {
     910                                //              $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
     911                                //      }
     912                                } else {
     913                                        $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
     914                                }
     915                        }
     916                }
     917
     918                if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
     919                        if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
     920                                $framebytelength = $this->FreeFormatFrameLength($offset, true);
     921                                if ($framebytelength > 0) {
     922                                        $thisfile_mpeg_audio['framelength'] = $framebytelength;
     923                                        if ($thisfile_mpeg_audio['layer'] == '1') {
     924                                                // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
     925                                                $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
     926                                        } else {
     927                                                // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
     928                                                $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
     929                                        }
     930                                } else {
     931                                        $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header';
     932                                }
     933                        }
     934                }
     935
     936                if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
     937                        switch ($thisfile_mpeg_audio['bitrate_mode']) {
     938                                case 'vbr':
     939                                case 'abr':
     940                                        $bytes_per_frame = 1152;
     941                                        if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
     942                                                $bytes_per_frame = 384;
     943                                        } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
     944                                                $bytes_per_frame = 576;
     945                                        }
     946                                        $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
     947                                        if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
     948                                                $info['audio']['bitrate']         = $thisfile_mpeg_audio['VBR_bitrate'];
     949                                                $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
     950                                        }
     951                                        break;
     952                        }
     953                }
     954
     955                // End variable-bitrate headers
     956                ////////////////////////////////////////////////////////////////////////////////////
     957
     958                if ($recursivesearch) {
     959
     960                        if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
     961                                return false;
     962                        }
     963
     964                }
     965
     966
     967                //if (false) {
     968                //    // experimental side info parsing section - not returning anything useful yet
     969                //
     970                //    $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
     971                //    $SideInfoOffset = 0;
     972                //
     973                //    if ($thisfile_mpeg_audio['version'] == '1') {
     974                //        if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
     975                //            // MPEG-1 (mono)
     976                //            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
     977                //            $SideInfoOffset += 9;
     978                //            $SideInfoOffset += 5;
     979                //        } else {
     980                //            // MPEG-1 (stereo, joint-stereo, dual-channel)
     981                //            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
     982                //            $SideInfoOffset += 9;
     983                //            $SideInfoOffset += 3;
     984                //        }
     985                //    } else { // 2 or 2.5
     986                //        if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
     987                //            // MPEG-2, MPEG-2.5 (mono)
     988                //            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
     989                //            $SideInfoOffset += 8;
     990                //            $SideInfoOffset += 1;
     991                //        } else {
     992                //            // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
     993                //            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
     994                //            $SideInfoOffset += 8;
     995                //            $SideInfoOffset += 2;
     996                //        }
     997                //    }
     998                //
     999                //    if ($thisfile_mpeg_audio['version'] == '1') {
     1000                //        for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
     1001                //            for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
     1002                //                $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1003                //                $SideInfoOffset += 2;
     1004                //            }
     1005                //        }
     1006                //    }
     1007                //    for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
     1008                //        for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
     1009                //            $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
     1010                //            $SideInfoOffset += 12;
     1011                //            $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
     1012                //            $SideInfoOffset += 9;
     1013                //            $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
     1014                //            $SideInfoOffset += 8;
     1015                //            if ($thisfile_mpeg_audio['version'] == '1') {
     1016                //                $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
     1017                //                $SideInfoOffset += 4;
     1018                //            } else {
     1019                //                $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
     1020                //                $SideInfoOffset += 9;
     1021                //            }
     1022                //            $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1023                //            $SideInfoOffset += 1;
     1024                //
     1025                //            if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
     1026                //
     1027                //                $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
     1028                //                $SideInfoOffset += 2;
     1029                //                $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1030                //                $SideInfoOffset += 1;
     1031                //
     1032                //                for ($region = 0; $region < 2; $region++) {
     1033                //                    $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
     1034                //                    $SideInfoOffset += 5;
     1035                //                }
     1036                //                $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
     1037                //
     1038                //                for ($window = 0; $window < 3; $window++) {
     1039                //                    $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
     1040                //                    $SideInfoOffset += 3;
     1041                //                }
     1042                //
     1043                //            } else {
     1044                //
     1045                //                for ($region = 0; $region < 3; $region++) {
     1046                //                    $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
     1047                //                    $SideInfoOffset += 5;
     1048                //                }
     1049                //
     1050                //                $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
     1051                //                $SideInfoOffset += 4;
     1052                //                $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
     1053                //                $SideInfoOffset += 3;
     1054                //                $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
     1055                //            }
     1056                //
     1057                //            if ($thisfile_mpeg_audio['version'] == '1') {
     1058                //                $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1059                //                $SideInfoOffset += 1;
     1060                //            }
     1061                //            $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1062                //            $SideInfoOffset += 1;
     1063                //            $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
     1064                //            $SideInfoOffset += 1;
     1065                //        }
     1066                //    }
     1067                //}
     1068
     1069                return true;
     1070        }
     1071
     1072        public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
     1073                $info = &$this->getid3->info;
     1074                $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
     1075                $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
     1076
     1077                for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) {
     1078                        // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
     1079                        if (($nextframetestoffset + 4) >= $info['avdataend']) {
     1080                                // end of file
     1081                                return true;
     1082                        }
     1083
     1084                        $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
     1085                        if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
     1086                                if ($ScanAsCBR) {
     1087                                        // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
     1088                                        if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
     1089                                                return false;
     1090                                        }
     1091                                }
     1092
     1093
     1094                                // next frame is OK, get ready to check the one after that
     1095                                if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
     1096                                        $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
     1097                                } else {
     1098                                        $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.';
     1099                                        return false;
     1100                                }
     1101
     1102                        } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {
     1103
     1104                                // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK
     1105                                return true;
     1106
     1107                        } else {
     1108
     1109                                // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
     1110                                $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.';
     1111
     1112                                return false;
     1113                        }
     1114                }
     1115                return true;
     1116        }
     1117
     1118        public function FreeFormatFrameLength($offset, $deepscan=false) {
     1119                $info = &$this->getid3->info;
     1120
     1121                fseek($this->getid3->fp, $offset, SEEK_SET);
     1122                $MPEGaudioData = fread($this->getid3->fp, 32768);
     1123
     1124                $SyncPattern1 = substr($MPEGaudioData, 0, 4);
     1125                // may be different pattern due to padding
     1126                $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3};
     1127                if ($SyncPattern2 === $SyncPattern1) {
     1128                        $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3};
     1129                }
     1130
     1131                $framelength = false;
     1132                $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
     1133                $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
     1134                if ($framelength1 > 4) {
     1135                        $framelength = $framelength1;
     1136                }
     1137                if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
     1138                        $framelength = $framelength2;
     1139                }
     1140                if (!$framelength) {
     1141
     1142                        // LAME 3.88 has a different value for modeextension on the first frame vs the rest
     1143                        $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
     1144                        $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
     1145
     1146                        if ($framelength1 > 4) {
     1147                                $framelength = $framelength1;
     1148                        }
     1149                        if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
     1150                                $framelength = $framelength2;
     1151                        }
     1152                        if (!$framelength) {
     1153                                $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset;
     1154                                return false;
     1155                        } else {
     1156                                $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
     1157                                $info['audio']['codec']   = 'LAME';
     1158                                $info['audio']['encoder'] = 'LAME3.88';
     1159                                $SyncPattern1 = substr($SyncPattern1, 0, 3);
     1160                                $SyncPattern2 = substr($SyncPattern2, 0, 3);
     1161                        }
     1162                }
     1163
     1164                if ($deepscan) {
     1165
     1166                        $ActualFrameLengthValues = array();
     1167                        $nextoffset = $offset + $framelength;
     1168                        while ($nextoffset < ($info['avdataend'] - 6)) {
     1169                                fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET);
     1170                                $NextSyncPattern = fread($this->getid3->fp, 6);
     1171                                if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
     1172                                        // good - found where expected
     1173                                        $ActualFrameLengthValues[] = $framelength;
     1174                                } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
     1175                                        // ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
     1176                                        $ActualFrameLengthValues[] = ($framelength - 1);
     1177                                        $nextoffset--;
     1178                                } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
     1179                                        // ok - found one byte later than expected (last frame was padded, first frame wasn't)
     1180                                        $ActualFrameLengthValues[] = ($framelength + 1);
     1181                                        $nextoffset++;
     1182                                } else {
     1183                                        $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset;
     1184                                        return false;
     1185                                }
     1186                                $nextoffset += $framelength;
     1187                        }
     1188                        if (count($ActualFrameLengthValues) > 0) {
     1189                                $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
     1190                        }
     1191                }
     1192                return $framelength;
     1193        }
     1194
     1195        public function getOnlyMPEGaudioInfoBruteForce() {
     1196                $MPEGaudioHeaderDecodeCache   = array();
     1197                $MPEGaudioHeaderValidCache    = array();
     1198                $MPEGaudioHeaderLengthCache   = array();
     1199                $MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
     1200                $MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
     1201                $MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
     1202                $MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
     1203                $MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
     1204                $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
     1205                $MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
     1206                $LongMPEGversionLookup        = array();
     1207                $LongMPEGlayerLookup          = array();
     1208                $LongMPEGbitrateLookup        = array();
     1209                $LongMPEGpaddingLookup        = array();
     1210                $LongMPEGfrequencyLookup      = array();
     1211                $Distribution['bitrate']      = array();
     1212                $Distribution['frequency']    = array();
     1213                $Distribution['layer']        = array();
     1214                $Distribution['version']      = array();
     1215                $Distribution['padding']      = array();
     1216
     1217                $info = &$this->getid3->info;
     1218                fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     1219
     1220                $max_frames_scan = 5000;
     1221                $frames_scanned  = 0;
     1222
     1223                $previousvalidframe = $info['avdataoffset'];
     1224                while (ftell($this->getid3->fp) < $info['avdataend']) {
     1225                        set_time_limit(30);
     1226                        $head4 = fread($this->getid3->fp, 4);
     1227                        if (strlen($head4) < 4) {
     1228                                break;
     1229                        }
     1230                        if ($head4{0} != "\xFF") {
     1231                                for ($i = 1; $i < 4; $i++) {
     1232                                        if ($head4{$i} == "\xFF") {
     1233                                                fseek($this->getid3->fp, $i - 4, SEEK_CUR);
     1234                                                continue 2;
     1235                                        }
     1236                                }
     1237                                continue;
     1238                        }
     1239                        if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
     1240                                $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4);
     1241                        }
     1242                        if (!isset($MPEGaudioHeaderValidCache[$head4])) {
     1243                                $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
     1244                        }
     1245                        if ($MPEGaudioHeaderValidCache[$head4]) {
     1246
     1247                                if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
     1248                                        $LongMPEGversionLookup[$head4]   = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
     1249                                        $LongMPEGlayerLookup[$head4]     = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
     1250                                        $LongMPEGbitrateLookup[$head4]   = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
     1251                                        $LongMPEGpaddingLookup[$head4]   = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
     1252                                        $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
     1253                                        $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength(
     1254                                                $LongMPEGbitrateLookup[$head4],
     1255                                                $LongMPEGversionLookup[$head4],
     1256                                                $LongMPEGlayerLookup[$head4],
     1257                                                $LongMPEGpaddingLookup[$head4],
     1258                                                $LongMPEGfrequencyLookup[$head4]);
     1259                                }
     1260                                if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
     1261                                        $WhereWeWere = ftell($this->getid3->fp);
     1262                                        fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
     1263                                        $next4 = fread($this->getid3->fp, 4);
     1264                                        if ($next4{0} == "\xFF") {
     1265                                                if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
     1266                                                        $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4);
     1267                                                }
     1268                                                if (!isset($MPEGaudioHeaderValidCache[$next4])) {
     1269                                                        $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
     1270                                                }
     1271                                                if ($MPEGaudioHeaderValidCache[$next4]) {
     1272                                                        fseek($this->getid3->fp, -4, SEEK_CUR);
     1273
     1274                                                        getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]);
     1275                                                        getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]);
     1276                                                        getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]);
     1277                                                        getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]);
     1278                                                        getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]);
     1279                                                        if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) {
     1280                                                                $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
     1281                                                                $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
     1282                                                                foreach ($Distribution as $key1 => $value1) {
     1283                                                                        foreach ($value1 as $key2 => $value2) {
     1284                                                                                $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
     1285                                                                        }
     1286                                                                }
     1287                                                                break;
     1288                                                        }
     1289                                                        continue;
     1290                                                }
     1291                                        }
     1292                                        unset($next4);
     1293                                        fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET);
     1294                                }
     1295
     1296                        }
     1297                }
     1298                foreach ($Distribution as $key => $value) {
     1299                        ksort($Distribution[$key], SORT_NUMERIC);
     1300                }
     1301                ksort($Distribution['version'], SORT_STRING);
     1302                $info['mpeg']['audio']['bitrate_distribution']   = $Distribution['bitrate'];
     1303                $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
     1304                $info['mpeg']['audio']['layer_distribution']     = $Distribution['layer'];
     1305                $info['mpeg']['audio']['version_distribution']   = $Distribution['version'];
     1306                $info['mpeg']['audio']['padding_distribution']   = $Distribution['padding'];
     1307                if (count($Distribution['version']) > 1) {
     1308                        $info['error'][] = 'Corrupt file - more than one MPEG version detected';
     1309                }
     1310                if (count($Distribution['layer']) > 1) {
     1311                        $info['error'][] = 'Corrupt file - more than one MPEG layer detected';
     1312                }
     1313                if (count($Distribution['frequency']) > 1) {
     1314                        $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected';
     1315                }
     1316
     1317
     1318                $bittotal = 0;
     1319                foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
     1320                        if ($bitratevalue != 'free') {
     1321                                $bittotal += ($bitratevalue * $bitratecount);
     1322                        }
     1323                }
     1324                $info['mpeg']['audio']['frame_count']  = array_sum($Distribution['bitrate']);
     1325                if ($info['mpeg']['audio']['frame_count'] == 0) {
     1326                        $info['error'][] = 'no MPEG audio frames found';
     1327                        return false;
     1328                }
     1329                $info['mpeg']['audio']['bitrate']      = ($bittotal / $info['mpeg']['audio']['frame_count']);
     1330                $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
     1331                $info['mpeg']['audio']['sample_rate']  = getid3_lib::array_max($Distribution['frequency'], true);
     1332
     1333                $info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
     1334                $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
     1335                $info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
     1336                $info['audio']['dataformat']   = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
     1337                $info['fileformat']            = $info['audio']['dataformat'];
     1338
     1339                return true;
     1340        }
     1341
     1342
     1343        public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
     1344                // looks for synch, decodes MPEG audio header
     1345
     1346                $info = &$this->getid3->info;
     1347
     1348                static $MPEGaudioVersionLookup;
     1349                static $MPEGaudioLayerLookup;
     1350                static $MPEGaudioBitrateLookup;
     1351                if (empty($MPEGaudioVersionLookup)) {
     1352                   $MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
     1353                   $MPEGaudioLayerLookup   = self::MPEGaudioLayerArray();
     1354                   $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
     1355
     1356                }
     1357
     1358                fseek($this->getid3->fp, $avdataoffset, SEEK_SET);
     1359                $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
     1360                if ($sync_seek_buffer_size <= 0) {
     1361                        $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset;
     1362                        return false;
     1363                }
     1364                $header = fread($this->getid3->fp, $sync_seek_buffer_size);
     1365                $sync_seek_buffer_size = strlen($header);
     1366                $SynchSeekOffset = 0;
     1367                while ($SynchSeekOffset < $sync_seek_buffer_size) {
     1368                        if ((($avdataoffset + $SynchSeekOffset)  < $info['avdataend']) && !feof($this->getid3->fp)) {
     1369
     1370                                if ($SynchSeekOffset > $sync_seek_buffer_size) {
     1371                                        // if a synch's not found within the first 128k bytes, then give up
     1372                                        $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB';
     1373                                        if (isset($info['audio']['bitrate'])) {
     1374                                                unset($info['audio']['bitrate']);
     1375                                        }
     1376                                        if (isset($info['mpeg']['audio'])) {
     1377                                                unset($info['mpeg']['audio']);
     1378                                        }
     1379                                        if (empty($info['mpeg'])) {
     1380                                                unset($info['mpeg']);
     1381                                        }
     1382                                        return false;
     1383
     1384                                } elseif (feof($this->getid3->fp)) {
     1385
     1386                                        $info['error'][] = 'Could not find valid MPEG audio synch before end of file';
     1387                                        if (isset($info['audio']['bitrate'])) {
     1388                                                unset($info['audio']['bitrate']);
     1389                                        }
     1390                                        if (isset($info['mpeg']['audio'])) {
     1391                                                unset($info['mpeg']['audio']);
     1392                                        }
     1393                                        if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) {
     1394                                                unset($info['mpeg']);
     1395                                        }
     1396                                        return false;
     1397                                }
     1398                        }
     1399
     1400                        if (($SynchSeekOffset + 1) >= strlen($header)) {
     1401                                $info['error'][] = 'Could not find valid MPEG synch before end of file';
     1402                                return false;
     1403                        }
     1404
     1405                        if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected
     1406                                if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
     1407                                        $FirstFrameThisfileInfo = $info;
     1408                                        $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
     1409                                        if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) {
     1410                                                // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
     1411                                                // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
     1412                                                unset($FirstFrameThisfileInfo);
     1413                                        }
     1414                                }
     1415
     1416                                $dummy = $info; // only overwrite real data if valid header found
     1417                                if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) {
     1418                                        $info = $dummy;
     1419                                        $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
     1420                                        switch (isset($info['fileformat']) ? $info['fileformat'] : '') {
     1421                                                case '':
     1422                                                case 'id3':
     1423                                                case 'ape':
     1424                                                case 'mp3':
     1425                                                        $info['fileformat']          = 'mp3';
     1426                                                        $info['audio']['dataformat'] = 'mp3';
     1427                                                        break;
     1428                                        }
     1429                                        if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
     1430                                                if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
     1431                                                        // If there is garbage data between a valid VBR header frame and a sequence
     1432                                                        // of valid MPEG-audio frames the VBR data is no longer discarded.
     1433                                                        $info = $FirstFrameThisfileInfo;
     1434                                                        $info['avdataoffset']        = $FirstFrameAVDataOffset;
     1435                                                        $info['fileformat']          = 'mp3';
     1436                                                        $info['audio']['dataformat'] = 'mp3';
     1437                                                        $dummy                       = $info;
     1438                                                        unset($dummy['mpeg']['audio']);
     1439                                                        $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
     1440                                                        $GarbageOffsetEnd   = $avdataoffset + $SynchSeekOffset;
     1441                                                        if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
     1442                                                                $info = $dummy;
     1443                                                                $info['avdataoffset'] = $GarbageOffsetEnd;
     1444                                                                $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd;
     1445                                                        } else {
     1446                                                                $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')';
     1447                                                        }
     1448                                                }
     1449                                        }
     1450                                        if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
     1451                                                // VBR file with no VBR header
     1452                                                $BitrateHistogram = true;
     1453                                        }
     1454
     1455                                        if ($BitrateHistogram) {
     1456
     1457                                                $info['mpeg']['audio']['stereo_distribution']  = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
     1458                                                $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
     1459
     1460                                                if ($info['mpeg']['audio']['version'] == '1') {
     1461                                                        if ($info['mpeg']['audio']['layer'] == 3) {
     1462                                                                $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0);
     1463                                                        } elseif ($info['mpeg']['audio']['layer'] == 2) {
     1464                                                                $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0);
     1465                                                        } elseif ($info['mpeg']['audio']['layer'] == 1) {
     1466                                                                $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0);
     1467                                                        }
     1468                                                } elseif ($info['mpeg']['audio']['layer'] == 1) {
     1469                                                        $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0);
     1470                                                } else {
     1471                                                        $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0);
     1472                                                }
     1473
     1474                                                $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
     1475                                                $synchstartoffset = $info['avdataoffset'];
     1476                                                fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     1477
     1478                                                // you can play with these numbers:
     1479                                                $max_frames_scan  = 50000;
     1480                                                $max_scan_segments = 10;
     1481
     1482                                                // don't play with these numbers:
     1483                                                $FastMode = false;
     1484                                                $SynchErrorsFound = 0;
     1485                                                $frames_scanned   = 0;
     1486                                                $this_scan_segment = 0;
     1487                                                $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments);
     1488                                                $pct_data_scanned = 0;
     1489                                                for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
     1490                                                        $frames_scanned_this_segment = 0;
     1491                                                        if (ftell($this->getid3->fp) >= $info['avdataend']) {
     1492                                                                break;
     1493                                                        }
     1494                                                        $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments)));
     1495                                                        if ($current_segment > 0) {
     1496                                                                fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET);
     1497                                                                $buffer_4k = fread($this->getid3->fp, 4096);
     1498                                                                for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) {
     1499                                                                        if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected
     1500                                                                                if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) {
     1501                                                                                        $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength'];
     1502                                                                                        if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) {
     1503                                                                                                $scan_start_offset[$current_segment] += $j;
     1504                                                                                                break;
     1505                                                                                        }
     1506                                                                                }
     1507                                                                        }
     1508                                                                }
     1509                                                        }
     1510                                                        $synchstartoffset = $scan_start_offset[$current_segment];
     1511                                                        while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
     1512                                                                $FastMode = true;
     1513                                                                $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
     1514
     1515                                                                if (empty($dummy['mpeg']['audio']['framelength'])) {
     1516                                                                        $SynchErrorsFound++;
     1517                                                                        $synchstartoffset++;
     1518                                                                } else {
     1519                                                                        getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]);
     1520                                                                        getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]);
     1521                                                                        getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]);
     1522                                                                        $synchstartoffset += $dummy['mpeg']['audio']['framelength'];
     1523                                                                }
     1524                                                                $frames_scanned++;
     1525                                                                if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
     1526                                                                        $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']);
     1527                                                                        if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
     1528                                                                                // file likely contains < $max_frames_scan, just scan as one segment
     1529                                                                                $max_scan_segments = 1;
     1530                                                                                $frames_scan_per_segment = $max_frames_scan;
     1531                                                                        } else {
     1532                                                                                $pct_data_scanned += $this_pct_scanned;
     1533                                                                                break;
     1534                                                                        }
     1535                                                                }
     1536                                                        }
     1537                                                }
     1538                                                if ($pct_data_scanned > 0) {
     1539                                                        $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
     1540                                                        foreach ($info['mpeg']['audio'] as $key1 => $value1) {
     1541                                                                if (!preg_match('#_distribution$#i', $key1)) {
     1542                                                                        continue;
     1543                                                                }
     1544                                                                foreach ($value1 as $key2 => $value2) {
     1545                                                                        $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned);
     1546                                                                }
     1547                                                        }
     1548                                                }
     1549
     1550                                                if ($SynchErrorsFound > 0) {
     1551                                                        $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis';
     1552                                                        //return false;
     1553                                                }
     1554
     1555                                                $bittotal     = 0;
     1556                                                $framecounter = 0;
     1557                                                foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
     1558                                                        $framecounter += $bitratecount;
     1559                                                        if ($bitratevalue != 'free') {
     1560                                                                $bittotal += ($bitratevalue * $bitratecount);
     1561                                                        }
     1562                                                }
     1563                                                if ($framecounter == 0) {
     1564                                                        $info['error'][] = 'Corrupt MP3 file: framecounter == zero';
     1565                                                        return false;
     1566                                                }
     1567                                                $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
     1568                                                $info['mpeg']['audio']['bitrate']     = ($bittotal / $framecounter);
     1569
     1570                                                $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
     1571
     1572
     1573                                                // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
     1574                                                $distinct_bitrates = 0;
     1575                                                foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
     1576                                                        if ($bitrate_count > 0) {
     1577                                                                $distinct_bitrates++;
     1578                                                        }
     1579                                                }
     1580                                                if ($distinct_bitrates > 1) {
     1581                                                        $info['mpeg']['audio']['bitrate_mode'] = 'vbr';
     1582                                                } else {
     1583                                                        $info['mpeg']['audio']['bitrate_mode'] = 'cbr';
     1584                                                }
     1585                                                $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
     1586
     1587                                        }
     1588
     1589                                        break; // exit while()
     1590                                }
     1591                        }
     1592
     1593                        $SynchSeekOffset++;
     1594                        if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) {
     1595                                // end of file/data
     1596
     1597                                if (empty($info['mpeg']['audio'])) {
     1598
     1599                                        $info['error'][] = 'could not find valid MPEG synch before end of file';
     1600                                        if (isset($info['audio']['bitrate'])) {
     1601                                                unset($info['audio']['bitrate']);
     1602                                        }
     1603                                        if (isset($info['mpeg']['audio'])) {
     1604                                                unset($info['mpeg']['audio']);
     1605                                        }
     1606                                        if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) {
     1607                                                unset($info['mpeg']);
     1608                                        }
     1609                                        return false;
     1610
     1611                                }
     1612                                break;
     1613                        }
     1614
     1615                }
     1616                $info['audio']['channels']        = $info['mpeg']['audio']['channels'];
     1617                $info['audio']['channelmode']     = $info['mpeg']['audio']['channelmode'];
     1618                $info['audio']['sample_rate']     = $info['mpeg']['audio']['sample_rate'];
     1619                return true;
     1620        }
     1621
     1622
     1623        public static function MPEGaudioVersionArray() {
     1624                static $MPEGaudioVersion = array('2.5', false, '2', '1');
     1625                return $MPEGaudioVersion;
     1626        }
     1627
     1628        public static function MPEGaudioLayerArray() {
     1629                static $MPEGaudioLayer = array(false, 3, 2, 1);
     1630                return $MPEGaudioLayer;
     1631        }
     1632
     1633        public static function MPEGaudioBitrateArray() {
     1634                static $MPEGaudioBitrate;
     1635                if (empty($MPEGaudioBitrate)) {
     1636                        $MPEGaudioBitrate = array (
     1637                                '1'  =>  array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000),
     1638                                                                2 => array('free', 32000, 48000, 56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000),
     1639                                                                3 => array('free', 32000, 40000, 48000,  56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000)
     1640                                                           ),
     1641
     1642                                '2'  =>  array (1 => array('free', 32000, 48000, 56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000),
     1643                                                                2 => array('free',  8000, 16000, 24000,  32000,  40000,  48000,  56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000),
     1644                                                           )
     1645                        );
     1646                        $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2];
     1647                        $MPEGaudioBitrate['2.5']  = $MPEGaudioBitrate['2'];
     1648                }
     1649                return $MPEGaudioBitrate;
     1650        }
     1651
     1652        public static function MPEGaudioFrequencyArray() {
     1653                static $MPEGaudioFrequency;
     1654                if (empty($MPEGaudioFrequency)) {
     1655                        $MPEGaudioFrequency = array (
     1656                                '1'   => array(44100, 48000, 32000),
     1657                                '2'   => array(22050, 24000, 16000),
     1658                                '2.5' => array(11025, 12000,  8000)
     1659                        );
     1660                }
     1661                return $MPEGaudioFrequency;
     1662        }
     1663
     1664        public static function MPEGaudioChannelModeArray() {
     1665                static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
     1666                return $MPEGaudioChannelMode;
     1667        }
     1668
     1669        public static function MPEGaudioModeExtensionArray() {
     1670                static $MPEGaudioModeExtension;
     1671                if (empty($MPEGaudioModeExtension)) {
     1672                        $MPEGaudioModeExtension = array (
     1673                                1 => array('4-31', '8-31', '12-31', '16-31'),
     1674                                2 => array('4-31', '8-31', '12-31', '16-31'),
     1675                                3 => array('', 'IS', 'MS', 'IS+MS')
     1676                        );
     1677                }
     1678                return $MPEGaudioModeExtension;
     1679        }
     1680
     1681        public static function MPEGaudioEmphasisArray() {
     1682                static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
     1683                return $MPEGaudioEmphasis;
     1684        }
     1685
     1686        public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
     1687                return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
     1688        }
     1689
     1690        public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
     1691                if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
     1692                        return false;
     1693                }
     1694
     1695                static $MPEGaudioVersionLookup;
     1696                static $MPEGaudioLayerLookup;
     1697                static $MPEGaudioBitrateLookup;
     1698                static $MPEGaudioFrequencyLookup;
     1699                static $MPEGaudioChannelModeLookup;
     1700                static $MPEGaudioModeExtensionLookup;
     1701                static $MPEGaudioEmphasisLookup;
     1702                if (empty($MPEGaudioVersionLookup)) {
     1703                        $MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
     1704                        $MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
     1705                        $MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
     1706                        $MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
     1707                        $MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
     1708                        $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
     1709                        $MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
     1710                }
     1711
     1712                if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
     1713                        $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
     1714                } else {
     1715                        echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : '');
     1716                        return false;
     1717                }
     1718                if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
     1719                        $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
     1720                } else {
     1721                        echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : '');
     1722                        return false;
     1723                }
     1724                if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
     1725                        echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : '');
     1726                        if ($rawarray['bitrate'] == 15) {
     1727                                // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
     1728                                // let it go through here otherwise file will not be identified
     1729                                if (!$allowBitrate15) {
     1730                                        return false;
     1731                                }
     1732                        } else {
     1733                                return false;
     1734                        }
     1735                }
     1736                if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
     1737                        echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : '');
     1738                        return false;
     1739                }
     1740                if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
     1741                        echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : '');
     1742                        return false;
     1743                }
     1744                if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
     1745                        echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : '');
     1746                        return false;
     1747                }
     1748                if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
     1749                        echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : '');
     1750                        return false;
     1751                }
     1752                // These are just either set or not set, you can't mess that up :)
     1753                // $rawarray['protection'];
     1754                // $rawarray['padding'];
     1755                // $rawarray['private'];
     1756                // $rawarray['copyright'];
     1757                // $rawarray['original'];
     1758
     1759                return true;
     1760        }
     1761
     1762        public static function MPEGaudioHeaderDecode($Header4Bytes) {
     1763                // AAAA AAAA  AAAB BCCD  EEEE FFGH  IIJJ KLMM
     1764                // A - Frame sync (all bits set)
     1765                // B - MPEG Audio version ID
     1766                // C - Layer description
     1767                // D - Protection bit
     1768                // E - Bitrate index
     1769                // F - Sampling rate frequency index
     1770                // G - Padding bit
     1771                // H - Private bit
     1772                // I - Channel Mode
     1773                // J - Mode extension (Only if Joint stereo)
     1774                // K - Copyright
     1775                // L - Original
     1776                // M - Emphasis
     1777
     1778                if (strlen($Header4Bytes) != 4) {
     1779                        return false;
     1780                }
     1781
     1782                $MPEGrawHeader['synch']         = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
     1783                $MPEGrawHeader['version']       = (ord($Header4Bytes{1}) & 0x18) >> 3; //    BB
     1784                $MPEGrawHeader['layer']         = (ord($Header4Bytes{1}) & 0x06) >> 1; //      CC
     1785                $MPEGrawHeader['protection']    = (ord($Header4Bytes{1}) & 0x01);      //        D
     1786                $MPEGrawHeader['bitrate']       = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE
     1787                $MPEGrawHeader['sample_rate']   = (ord($Header4Bytes{2}) & 0x0C) >> 2; //     FF
     1788                $MPEGrawHeader['padding']       = (ord($Header4Bytes{2}) & 0x02) >> 1; //       G
     1789                $MPEGrawHeader['private']       = (ord($Header4Bytes{2}) & 0x01);      //        H
     1790                $MPEGrawHeader['channelmode']   = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II
     1791                $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; //   JJ
     1792                $MPEGrawHeader['copyright']     = (ord($Header4Bytes{3}) & 0x08) >> 3; //     K
     1793                $MPEGrawHeader['original']      = (ord($Header4Bytes{3}) & 0x04) >> 2; //      L
     1794                $MPEGrawHeader['emphasis']      = (ord($Header4Bytes{3}) & 0x03);      //       MM
     1795
     1796                return $MPEGrawHeader;
     1797        }
     1798
     1799        public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
     1800                static $AudioFrameLengthCache = array();
     1801
     1802                if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
     1803                        $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
     1804                        if ($bitrate != 'free') {
     1805
     1806                                if ($version == '1') {
     1807
     1808                                        if ($layer == '1') {
     1809
     1810                                                // For Layer I slot is 32 bits long
     1811                                                $FrameLengthCoefficient = 48;
     1812                                                $SlotLength = 4;
     1813
     1814                                        } else { // Layer 2 / 3
     1815
     1816                                                // for Layer 2 and Layer 3 slot is 8 bits long.
     1817                                                $FrameLengthCoefficient = 144;
     1818                                                $SlotLength = 1;
     1819
     1820                                        }
     1821
     1822                                } else { // MPEG-2 / MPEG-2.5
     1823
     1824                                        if ($layer == '1') {
     1825
     1826                                                // For Layer I slot is 32 bits long
     1827                                                $FrameLengthCoefficient = 24;
     1828                                                $SlotLength = 4;
     1829
     1830                                        } elseif ($layer == '2') {
     1831
     1832                                                // for Layer 2 and Layer 3 slot is 8 bits long.
     1833                                                $FrameLengthCoefficient = 144;
     1834                                                $SlotLength = 1;
     1835
     1836                                        } else { // layer 3
     1837
     1838                                                // for Layer 2 and Layer 3 slot is 8 bits long.
     1839                                                $FrameLengthCoefficient = 72;
     1840                                                $SlotLength = 1;
     1841
     1842                                        }
     1843
     1844                                }
     1845
     1846                                // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
     1847                                if ($samplerate > 0) {
     1848                                        $NewFramelength  = ($FrameLengthCoefficient * $bitrate) / $samplerate;
     1849                                        $NewFramelength  = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I)
     1850                                        if ($padding) {
     1851                                                $NewFramelength += $SlotLength;
     1852                                        }
     1853                                        $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
     1854                                }
     1855                        }
     1856                }
     1857                return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
     1858        }
     1859
     1860        public static function ClosestStandardMP3Bitrate($bit_rate) {
     1861                static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
     1862                static $bit_rate_table = array (0=>'-');
     1863                $round_bit_rate = intval(round($bit_rate, -3));
     1864                if (!isset($bit_rate_table[$round_bit_rate])) {
     1865                        if ($round_bit_rate > max($standard_bit_rates)) {
     1866                                $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate));
     1867                        } else {
     1868                                $bit_rate_table[$round_bit_rate] = max($standard_bit_rates);
     1869                                foreach ($standard_bit_rates as $standard_bit_rate) {
     1870                                        if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) {
     1871                                                break;
     1872                                        }
     1873                                        $bit_rate_table[$round_bit_rate] = $standard_bit_rate;
     1874                                }
     1875                        }
     1876                }
     1877                return $bit_rate_table[$round_bit_rate];
     1878        }
     1879
     1880        public static function XingVBRidOffset($version, $channelmode) {
     1881                static $XingVBRidOffsetCache = array();
     1882                if (empty($XingVBRidOffset)) {
     1883                        $XingVBRidOffset = array (
     1884                                '1'   => array ('mono'          => 0x15, // 4 + 17 = 21
     1885                                                                'stereo'        => 0x24, // 4 + 32 = 36
     1886                                                                'joint stereo'  => 0x24,
     1887                                                                'dual channel'  => 0x24
     1888                                                           ),
     1889
     1890                                '2'   => array ('mono'          => 0x0D, // 4 +  9 = 13
     1891                                                                'stereo'        => 0x15, // 4 + 17 = 21
     1892                                                                'joint stereo'  => 0x15,
     1893                                                                'dual channel'  => 0x15
     1894                                                           ),
     1895
     1896                                '2.5' => array ('mono'          => 0x15,
     1897                                                                'stereo'        => 0x15,
     1898                                                                'joint stereo'  => 0x15,
     1899                                                                'dual channel'  => 0x15
     1900                                                           )
     1901                        );
     1902                }
     1903                return $XingVBRidOffset[$version][$channelmode];
     1904        }
     1905
     1906        public static function LAMEvbrMethodLookup($VBRmethodID) {
     1907                static $LAMEvbrMethodLookup = array(
     1908                        0x00 => 'unknown',
     1909                        0x01 => 'cbr',
     1910                        0x02 => 'abr',
     1911                        0x03 => 'vbr-old / vbr-rh',
     1912                        0x04 => 'vbr-new / vbr-mtrh',
     1913                        0x05 => 'vbr-mt',
     1914                        0x06 => 'vbr (full vbr method 4)',
     1915                        0x08 => 'cbr (constant bitrate 2 pass)',
     1916                        0x09 => 'abr (2 pass)',
     1917                        0x0F => 'reserved'
     1918                );
     1919                return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
     1920        }
     1921
     1922        public static function LAMEmiscStereoModeLookup($StereoModeID) {
     1923                static $LAMEmiscStereoModeLookup = array(
     1924                        0 => 'mono',
     1925                        1 => 'stereo',
     1926                        2 => 'dual mono',
     1927                        3 => 'joint stereo',
     1928                        4 => 'forced stereo',
     1929                        5 => 'auto',
     1930                        6 => 'intensity stereo',
     1931                        7 => 'other'
     1932                );
     1933                return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
     1934        }
     1935
     1936        public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
     1937                static $LAMEmiscSourceSampleFrequencyLookup = array(
     1938                        0 => '<= 32 kHz',
     1939                        1 => '44.1 kHz',
     1940                        2 => '48 kHz',
     1941                        3 => '> 48kHz'
     1942                );
     1943                return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
     1944        }
     1945
     1946        public static function LAMEsurroundInfoLookup($SurroundInfoID) {
     1947                static $LAMEsurroundInfoLookup = array(
     1948                        0 => 'no surround info',
     1949                        1 => 'DPL encoding',
     1950                        2 => 'DPL2 encoding',
     1951                        3 => 'Ambisonic encoding'
     1952                );
     1953                return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
     1954        }
     1955
     1956        public static function LAMEpresetUsedLookup($LAMEtag) {
     1957
     1958                if ($LAMEtag['preset_used_id'] == 0) {
     1959                        // no preset used (LAME >=3.93)
     1960                        // no preset recorded (LAME <3.93)
     1961                        return '';
     1962                }
     1963                $LAMEpresetUsedLookup = array();
     1964
     1965                /////  THIS PART CANNOT BE STATIC .
     1966                for ($i = 8; $i <= 320; $i++) {
     1967                        switch ($LAMEtag['vbr_method']) {
     1968                                case 'cbr':
     1969                                        $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i;
     1970                                        break;
     1971                                case 'abr':
     1972                                default: // other VBR modes shouldn't be here(?)
     1973                                        $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i;
     1974                                        break;
     1975                        }
     1976                }
     1977
     1978                // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions()
     1979
     1980                // named alt-presets
     1981                $LAMEpresetUsedLookup[1000] = '--r3mix';
     1982                $LAMEpresetUsedLookup[1001] = '--alt-preset standard';
     1983                $LAMEpresetUsedLookup[1002] = '--alt-preset extreme';
     1984                $LAMEpresetUsedLookup[1003] = '--alt-preset insane';
     1985                $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard';
     1986                $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme';
     1987                $LAMEpresetUsedLookup[1006] = '--alt-preset medium';
     1988                $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium';
     1989
     1990                // LAME 3.94 additions/changes
     1991                $LAMEpresetUsedLookup[1010] = '--preset portable';                                                           // 3.94a15 Oct 21 2003
     1992                $LAMEpresetUsedLookup[1015] = '--preset radio';                                                              // 3.94a15 Oct 21 2003
     1993
     1994                $LAMEpresetUsedLookup[320]  = '--preset insane';                                                             // 3.94a15 Nov 12 2003
     1995                $LAMEpresetUsedLookup[410]  = '-V9';
     1996                $LAMEpresetUsedLookup[420]  = '-V8';
     1997                $LAMEpresetUsedLookup[440]  = '-V6';
     1998                $LAMEpresetUsedLookup[430]  = '--preset radio';                                                              // 3.94a15 Nov 12 2003
     1999                $LAMEpresetUsedLookup[450]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable';  // 3.94a15 Nov 12 2003
     2000                $LAMEpresetUsedLookup[460]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium';    // 3.94a15 Nov 12 2003
     2001                $LAMEpresetUsedLookup[470]  = '--r3mix';                                                                     // 3.94b1  Dec 18 2003
     2002                $LAMEpresetUsedLookup[480]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard';  // 3.94a15 Nov 12 2003
     2003                $LAMEpresetUsedLookup[490]  = '-V1';
     2004                $LAMEpresetUsedLookup[500]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme';   // 3.94a15 Nov 12 2003
     2005
     2006                return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org');
     2007        }
     2008
     2009}
  • new file wp-includes/ID3/module.audio.ogg.php

    diff --git wp-includes/ID3/module.audio.ogg.php wp-includes/ID3/module.audio.ogg.php
    new file mode 100644
    index 0000000..a2a35aa
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.audio.ogg.php                                        //
     11// module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
     12// dependencies: module.audio.flac.php                         //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
     17
     18class getid3_ogg extends getid3_handler
     19{
     20        // http://xiph.org/vorbis/doc/Vorbis_I_spec.html
     21        public function Analyze() {
     22                $info = &$this->getid3->info;
     23
     24                $info['fileformat'] = 'ogg';
     25
     26                // Warn about illegal tags - only vorbiscomments are allowed
     27                if (isset($info['id3v2'])) {
     28                        $info['warning'][] = 'Illegal ID3v2 tag present.';
     29                }
     30                if (isset($info['id3v1'])) {
     31                        $info['warning'][] = 'Illegal ID3v1 tag present.';
     32                }
     33                if (isset($info['ape'])) {
     34                        $info['warning'][] = 'Illegal APE tag present.';
     35                }
     36
     37
     38                // Page 1 - Stream Header
     39
     40                $this->fseek($info['avdataoffset']);
     41
     42                $oggpageinfo = $this->ParseOggPageHeader();
     43                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
     44
     45                if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
     46                        $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
     47                        unset($info['fileformat']);
     48                        unset($info['ogg']);
     49                        return false;
     50                }
     51
     52                $filedata = $this->fread($oggpageinfo['page_length']);
     53                $filedataoffset = 0;
     54
     55                if (substr($filedata, 0, 4) == 'fLaC') {
     56
     57                        $info['audio']['dataformat']   = 'flac';
     58                        $info['audio']['bitrate_mode'] = 'vbr';
     59                        $info['audio']['lossless']     = true;
     60
     61                } elseif (substr($filedata, 1, 6) == 'vorbis') {
     62
     63                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
     64
     65                } elseif (substr($filedata, 0, 8) == 'Speex   ') {
     66
     67                        // http://www.speex.org/manual/node10.html
     68
     69                        $info['audio']['dataformat']   = 'speex';
     70                        $info['mime_type']             = 'audio/speex';
     71                        $info['audio']['bitrate_mode'] = 'abr';
     72                        $info['audio']['lossless']     = false;
     73
     74                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
     75                        $filedataoffset += 8;
     76                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
     77                        $filedataoffset += 20;
     78                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     79                        $filedataoffset += 4;
     80                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     81                        $filedataoffset += 4;
     82                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     83                        $filedataoffset += 4;
     84                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     85                        $filedataoffset += 4;
     86                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     87                        $filedataoffset += 4;
     88                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     89                        $filedataoffset += 4;
     90                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     91                        $filedataoffset += 4;
     92                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     93                        $filedataoffset += 4;
     94                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     95                        $filedataoffset += 4;
     96                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     97                        $filedataoffset += 4;
     98                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     99                        $filedataoffset += 4;
     100                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     101                        $filedataoffset += 4;
     102                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     103                        $filedataoffset += 4;
     104
     105                        $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
     106                        $info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
     107                        $info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
     108                        $info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
     109                        $info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
     110
     111                        $info['audio']['sample_rate']   = $info['speex']['sample_rate'];
     112                        $info['audio']['channels']      = $info['speex']['channels'];
     113                        if ($info['speex']['vbr']) {
     114                                $info['audio']['bitrate_mode'] = 'vbr';
     115                        }
     116
     117
     118                } elseif (substr($filedata, 0, 8) == "fishead\x00") {
     119
     120                        // Ogg Skeleton version 3.0 Format Specification
     121                        // http://xiph.org/ogg/doc/skeleton.html
     122                        $filedataoffset += 8;
     123                        $info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
     124                        $filedataoffset += 2;
     125                        $info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
     126                        $filedataoffset += 2;
     127                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     128                        $filedataoffset += 8;
     129                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     130                        $filedataoffset += 8;
     131                        $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     132                        $filedataoffset += 8;
     133                        $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     134                        $filedataoffset += 8;
     135                        $info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
     136                        $filedataoffset += 20;
     137
     138                        $info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
     139                        $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
     140                        $info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
     141                        $info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
     142
     143
     144                        $counter = 0;
     145                        do {
     146                                $oggpageinfo = $this->ParseOggPageHeader();
     147                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
     148                                $filedata = $this->fread($oggpageinfo['page_length']);
     149                                $this->fseek($oggpageinfo['page_end_offset']);
     150
     151                                if (substr($filedata, 0, 8) == "fisbone\x00") {
     152
     153                                        $filedataoffset = 8;
     154                                        $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
     155                                        $filedataoffset += 4;
     156                                        $info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
     157                                        $filedataoffset += 4;
     158                                        $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
     159                                        $filedataoffset += 4;
     160                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     161                                        $filedataoffset += 8;
     162                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     163                                        $filedataoffset += 8;
     164                                        $info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
     165                                        $filedataoffset += 8;
     166                                        $info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
     167                                        $filedataoffset += 4;
     168                                        $info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
     169                                        $filedataoffset += 1;
     170                                        $info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
     171                                        $filedataoffset += 3;
     172
     173                                } elseif (substr($filedata, 1, 6) == 'theora') {
     174
     175                                        $info['video']['dataformat'] = 'theora';
     176                                        $info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
     177                                        //break;
     178
     179                                } elseif (substr($filedata, 1, 6) == 'vorbis') {
     180
     181                                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
     182
     183                                } else {
     184                                        $info['error'][] = 'unexpected';
     185                                        //break;
     186                                }
     187                        //} while ($oggpageinfo['page_seqno'] == 0);
     188                        } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
     189
     190                        $this->fseek($oggpageinfo['page_start_offset']);
     191
     192                        $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
     193                        //return false;
     194
     195                } else {
     196
     197                        $info['error'][] = 'Expecting either "Speex   " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
     198                        unset($info['ogg']);
     199                        unset($info['mime_type']);
     200                        return false;
     201
     202                }
     203
     204                // Page 2 - Comment Header
     205                $oggpageinfo = $this->ParseOggPageHeader();
     206                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
     207
     208                switch ($info['audio']['dataformat']) {
     209                        case 'vorbis':
     210                                $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
     211                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
     212                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
     213
     214                                $this->ParseVorbisComments();
     215                                break;
     216
     217                        case 'flac':
     218                                $flac = new getid3_flac($this->getid3);
     219                                if (!$flac->parseMETAdata()) {
     220                                        $info['error'][] = 'Failed to parse FLAC headers';
     221                                        return false;
     222                                }
     223                                unset($flac);
     224                                break;
     225
     226                        case 'speex':
     227                                $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
     228                                $this->ParseVorbisComments();
     229                                break;
     230                }
     231
     232
     233                // Last Page - Number of Samples
     234                if (!getid3_lib::intValueSupported($info['avdataend'])) {
     235
     236                        $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
     237
     238                } else {
     239
     240                        $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
     241                        $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
     242                        if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
     243                                $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
     244                                $info['avdataend'] = $this->ftell();
     245                                $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
     246                                $info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
     247                                if ($info['ogg']['samples'] == 0) {
     248                                        $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
     249                                        return false;
     250                                }
     251                                if (!empty($info['audio']['sample_rate'])) {
     252                                        $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
     253                                }
     254                        }
     255
     256                }
     257
     258                if (!empty($info['ogg']['bitrate_average'])) {
     259                        $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
     260                } elseif (!empty($info['ogg']['bitrate_nominal'])) {
     261                        $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
     262                } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
     263                        $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
     264                }
     265                if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
     266                        if ($info['audio']['bitrate'] == 0) {
     267                                $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
     268                                return false;
     269                        }
     270                        $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
     271                }
     272
     273                if (isset($info['ogg']['vendor'])) {
     274                        $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
     275
     276                        // Vorbis only
     277                        if ($info['audio']['dataformat'] == 'vorbis') {
     278
     279                                // Vorbis 1.0 starts with Xiph.Org
     280                                if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
     281
     282                                        if ($info['audio']['bitrate_mode'] == 'abr') {
     283
     284                                                // Set -b 128 on abr files
     285                                                $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
     286
     287                                        } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
     288                                                // Set -q N on vbr files
     289                                                $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
     290
     291                                        }
     292                                }
     293
     294                                if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
     295                                        $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
     296                                }
     297                        }
     298                }
     299
     300                return true;
     301        }
     302
     303        public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
     304                $info = &$this->getid3->info;
     305                $info['audio']['dataformat'] = 'vorbis';
     306                $info['audio']['lossless']   = false;
     307
     308                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     309                $filedataoffset += 1;
     310                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
     311                $filedataoffset += 6;
     312                $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     313                $filedataoffset += 4;
     314                $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     315                $filedataoffset += 1;
     316                $info['audio']['channels']       = $info['ogg']['numberofchannels'];
     317                $info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     318                $filedataoffset += 4;
     319                if ($info['ogg']['samplerate'] == 0) {
     320                        $info['error'][] = 'Corrupt Ogg file: sample rate == zero';
     321                        return false;
     322                }
     323                $info['audio']['sample_rate']    = $info['ogg']['samplerate'];
     324                $info['ogg']['samples']          = 0; // filled in later
     325                $info['ogg']['bitrate_average']  = 0; // filled in later
     326                $info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     327                $filedataoffset += 4;
     328                $info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     329                $filedataoffset += 4;
     330                $info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     331                $filedataoffset += 4;
     332                $info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
     333                $info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
     334                $info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
     335
     336                $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
     337                if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
     338                        unset($info['ogg']['bitrate_max']);
     339                        $info['audio']['bitrate_mode'] = 'abr';
     340                }
     341                if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
     342                        unset($info['ogg']['bitrate_nominal']);
     343                }
     344                if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
     345                        unset($info['ogg']['bitrate_min']);
     346                        $info['audio']['bitrate_mode'] = 'abr';
     347                }
     348                return true;
     349        }
     350
     351        public function ParseOggPageHeader() {
     352                // http://xiph.org/ogg/vorbis/doc/framing.html
     353                $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
     354
     355                $filedata = $this->fread($this->getid3->fread_buffer_size());
     356                $filedataoffset = 0;
     357                while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
     358                        if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
     359                                // should be found before here
     360                                return false;
     361                        }
     362                        if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
     363                                if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
     364                                        // get some more data, unless eof, in which case fail
     365                                        return false;
     366                                }
     367                        }
     368                }
     369                $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
     370
     371                $oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     372                $filedataoffset += 1;
     373                $oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     374                $filedataoffset += 1;
     375                $oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
     376                $oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
     377                $oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
     378
     379                $oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
     380                $filedataoffset += 8;
     381                $oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     382                $filedataoffset += 4;
     383                $oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     384                $filedataoffset += 4;
     385                $oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
     386                $filedataoffset += 4;
     387                $oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     388                $filedataoffset += 1;
     389                $oggheader['page_length'] = 0;
     390                for ($i = 0; $i < $oggheader['page_segments']; $i++) {
     391                        $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
     392                        $filedataoffset += 1;
     393                        $oggheader['page_length'] += $oggheader['segment_table'][$i];
     394                }
     395                $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
     396                $oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
     397                $this->fseek($oggheader['header_end_offset']);
     398
     399                return $oggheader;
     400        }
     401
     402    // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
     403        public function ParseVorbisComments() {
     404                $info = &$this->getid3->info;
     405
     406                $OriginalOffset = $this->ftell();
     407                $commentdataoffset = 0;
     408                $VorbisCommentPage = 1;
     409
     410                switch ($info['audio']['dataformat']) {
     411                        case 'vorbis':
     412                        case 'speex':
     413                                $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
     414                                $this->fseek($CommentStartOffset);
     415                                $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
     416                                $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
     417
     418                                if ($info['audio']['dataformat'] == 'vorbis') {
     419                                        $commentdataoffset += (strlen('vorbis') + 1);
     420                                }
     421                                break;
     422
     423                        case 'flac':
     424                                $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
     425                                $this->fseek($CommentStartOffset);
     426                                $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
     427                                break;
     428
     429                        default:
     430                                return false;
     431                }
     432
     433                $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
     434                $commentdataoffset += 4;
     435
     436                $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
     437                $commentdataoffset += $VendorSize;
     438
     439                $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
     440                $commentdataoffset += 4;
     441                $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
     442
     443                $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
     444                $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
     445                for ($i = 0; $i < $CommentsCount; $i++) {
     446
     447                        $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
     448
     449                        if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
     450                                if ($oggpageinfo = $this->ParseOggPageHeader()) {
     451                                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
     452
     453                                        $VorbisCommentPage++;
     454
     455                                        // First, save what we haven't read yet
     456                                        $AsYetUnusedData = substr($commentdata, $commentdataoffset);
     457
     458                                        // Then take that data off the end
     459                                        $commentdata     = substr($commentdata, 0, $commentdataoffset);
     460
     461                                        // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
     462                                        $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
     463                                        $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
     464
     465                                        // Finally, stick the unused data back on the end
     466                                        $commentdata .= $AsYetUnusedData;
     467
     468                                        //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
     469                                        $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
     470                                }
     471
     472                        }
     473                        $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
     474
     475                        // replace avdataoffset with position just after the last vorbiscomment
     476                        $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
     477
     478                        $commentdataoffset += 4;
     479                        while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
     480                                if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
     481                                        $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
     482                                        break 2;
     483                                }
     484
     485                                $VorbisCommentPage++;
     486
     487                                $oggpageinfo = $this->ParseOggPageHeader();
     488                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
     489
     490                                // First, save what we haven't read yet
     491                                $AsYetUnusedData = substr($commentdata, $commentdataoffset);
     492
     493                                // Then take that data off the end
     494                                $commentdata     = substr($commentdata, 0, $commentdataoffset);
     495
     496                                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
     497                                $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
     498                                $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
     499
     500                                // Finally, stick the unused data back on the end
     501                                $commentdata .= $AsYetUnusedData;
     502
     503                                //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
     504                                if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
     505                                        $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
     506                                        break;
     507                                }
     508                                $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
     509                                if ($readlength <= 0) {
     510                                        $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
     511                                        break;
     512                                }
     513                                $commentdata .= $this->fread($readlength);
     514
     515                                //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
     516                        }
     517                        $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
     518                        $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
     519                        $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
     520
     521                        if (!$commentstring) {
     522
     523                                // no comment?
     524                                $info['warning'][] = 'Blank Ogg comment ['.$i.']';
     525
     526                        } elseif (strstr($commentstring, '=')) {
     527
     528                                $commentexploded = explode('=', $commentstring, 2);
     529                                $ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
     530                                $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
     531
     532                                if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
     533
     534                                        // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
     535                                        // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
     536                                        // http://flac.sourceforge.net/format.html#metadata_block_picture
     537                                        $flac = new getid3_flac($this->getid3);
     538                                        $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
     539                                        $flac->parsePICTURE();
     540                                        $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
     541                                        unset($flac);
     542
     543                                } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
     544
     545                                        $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
     546                                        $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
     547                                        /** @todo use 'coverartmime' where available */
     548                                        $imageinfo = getid3_lib::GetDataImageSize($data);
     549                                        if ($imageinfo === false || !isset($imageinfo['mime'])) {
     550                                                $this->warning('COVERART vorbiscomment tag contains invalid image');
     551                                                continue;
     552                                        }
     553
     554                                        $ogg = new self($this->getid3);
     555                                        $ogg->setStringMode($data);
     556                                        $info['ogg']['comments']['picture'][] = array(
     557                                                'image_mime' => $imageinfo['mime'],
     558                                                'data'       => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
     559                                        );
     560                                        unset($ogg);
     561
     562                                } else {
     563
     564                                        $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
     565
     566                                }
     567
     568                        } else {
     569
     570                                $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
     571
     572                        }
     573                        unset($ThisFileInfo_ogg_comments_raw[$i]);
     574                }
     575                unset($ThisFileInfo_ogg_comments_raw);
     576
     577
     578                // Replay Gain Adjustment
     579                // http://privatewww.essex.ac.uk/~djmrob/replaygain/
     580                if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
     581                        foreach ($info['ogg']['comments'] as $index => $commentvalue) {
     582                                switch ($index) {
     583                                        case 'rg_audiophile':
     584                                        case 'replaygain_album_gain':
     585                                                $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
     586                                                unset($info['ogg']['comments'][$index]);
     587                                                break;
     588
     589                                        case 'rg_radio':
     590                                        case 'replaygain_track_gain':
     591                                                $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
     592                                                unset($info['ogg']['comments'][$index]);
     593                                                break;
     594
     595                                        case 'replaygain_album_peak':
     596                                                $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
     597                                                unset($info['ogg']['comments'][$index]);
     598                                                break;
     599
     600                                        case 'rg_peak':
     601                                        case 'replaygain_track_peak':
     602                                                $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
     603                                                unset($info['ogg']['comments'][$index]);
     604                                                break;
     605
     606                                        case 'replaygain_reference_loudness':
     607                                                $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
     608                                                unset($info['ogg']['comments'][$index]);
     609                                                break;
     610
     611                                        default:
     612                                                // do nothing
     613                                                break;
     614                                }
     615                        }
     616                }
     617
     618                $this->fseek($OriginalOffset);
     619
     620                return true;
     621        }
     622
     623        public static function SpeexBandModeLookup($mode) {
     624                static $SpeexBandModeLookup = array();
     625                if (empty($SpeexBandModeLookup)) {
     626                        $SpeexBandModeLookup[0] = 'narrow';
     627                        $SpeexBandModeLookup[1] = 'wide';
     628                        $SpeexBandModeLookup[2] = 'ultra-wide';
     629                }
     630                return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
     631        }
     632
     633
     634        public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
     635                for ($i = 0; $i < $SegmentNumber; $i++) {
     636                        $segmentlength = 0;
     637                        foreach ($OggInfoArray['segment_table'] as $key => $value) {
     638                                $segmentlength += $value;
     639                                if ($value < 255) {
     640                                        break;
     641                                }
     642                        }
     643                }
     644                return $segmentlength;
     645        }
     646
     647
     648        public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
     649
     650                // decrease precision
     651                $nominal_bitrate = $nominal_bitrate / 1000;
     652
     653                if ($nominal_bitrate < 128) {
     654                        // q-1 to q4
     655                        $qval = ($nominal_bitrate - 64) / 16;
     656                } elseif ($nominal_bitrate < 256) {
     657                        // q4 to q8
     658                        $qval = $nominal_bitrate / 32;
     659                } elseif ($nominal_bitrate < 320) {
     660                        // q8 to q9
     661                        $qval = ($nominal_bitrate + 256) / 64;
     662                } else {
     663                        // q9 to q10
     664                        $qval = ($nominal_bitrate + 1300) / 180;
     665                }
     666                //return $qval; // 5.031324
     667                //return intval($qval); // 5
     668                return round($qval, 1); // 5 or 4.9
     669        }
     670
     671}
  • new file wp-includes/ID3/module.tag.apetag.php

    diff --git wp-includes/ID3/module.tag.apetag.php wp-includes/ID3/module.tag.apetag.php
    new file mode 100644
    index 0000000..afeede7
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.tag.apetag.php                                       //
     11// module for analyzing APE tags                               //
     12// dependencies: NONE                                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16class getid3_apetag extends getid3_handler
     17{
     18        public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
     19        public $overrideendoffset  = 0;
     20
     21        public function Analyze() {
     22                $info = &$this->getid3->info;
     23
     24                if (!getid3_lib::intValueSupported($info['filesize'])) {
     25                        $info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
     26                        return false;
     27                }
     28
     29                $id3v1tagsize     = 128;
     30                $apetagheadersize = 32;
     31                $lyrics3tagsize   = 10;
     32
     33                if ($this->overrideendoffset == 0) {
     34
     35                        fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
     36                        $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
     37
     38                        //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
     39                        if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
     40
     41                                // APE tag found before ID3v1
     42                                $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
     43
     44                        //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
     45                        } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
     46
     47                                // APE tag found, no ID3v1
     48                                $info['ape']['tag_offset_end'] = $info['filesize'];
     49
     50                        }
     51
     52                } else {
     53
     54                        fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
     55                        if (fread($this->getid3->fp, 8) == 'APETAGEX') {
     56                                $info['ape']['tag_offset_end'] = $this->overrideendoffset;
     57                        }
     58
     59                }
     60                if (!isset($info['ape']['tag_offset_end'])) {
     61
     62                        // APE tag not found
     63                        unset($info['ape']);
     64                        return false;
     65
     66                }
     67
     68                // shortcut
     69                $thisfile_ape = &$info['ape'];
     70
     71                fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
     72                $APEfooterData = fread($this->getid3->fp, 32);
     73                if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
     74                        $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
     75                        return false;
     76                }
     77
     78                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
     79                        fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
     80                        $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
     81                        $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
     82                } else {
     83                        $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
     84                        fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
     85                        $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
     86                }
     87                $info['avdataend'] = $thisfile_ape['tag_offset_start'];
     88
     89                if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
     90                        $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
     91                        unset($info['id3v1']);
     92                        foreach ($info['warning'] as $key => $value) {
     93                                if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
     94                                        unset($info['warning'][$key]);
     95                                        sort($info['warning']);
     96                                        break;
     97                                }
     98                        }
     99                }
     100
     101                $offset = 0;
     102                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
     103                        if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
     104                                $offset += $apetagheadersize;
     105                        } else {
     106                                $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
     107                                return false;
     108                        }
     109                }
     110
     111                // shortcut
     112                $info['replay_gain'] = array();
     113                $thisfile_replaygain = &$info['replay_gain'];
     114
     115                for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
     116                        $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
     117                        $offset += 4;
     118                        $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
     119                        $offset += 4;
     120                        if (strstr(substr($APEtagData, $offset), "\x00") === false) {
     121                                $info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
     122                                return false;
     123                        }
     124                        $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
     125                        $item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
     126
     127                        // shortcut
     128                        $thisfile_ape['items'][$item_key] = array();
     129                        $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
     130
     131                        $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
     132
     133                        $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
     134                        $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
     135                        $offset += $value_size;
     136
     137                        $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
     138                        switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
     139                                case 0: // UTF-8
     140                                case 3: // Locator (URL, filename, etc), UTF-8 encoded
     141                                        $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
     142                                        break;
     143
     144                                default: // binary data
     145                                        break;
     146                        }
     147
     148                        switch (strtolower($item_key)) {
     149                                case 'replaygain_track_gain':
     150                                        $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
     151                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
     152                                        break;
     153
     154                                case 'replaygain_track_peak':
     155                                        $thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
     156                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
     157                                        if ($thisfile_replaygain['track']['peak'] <= 0) {
     158                                                $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
     159                                        }
     160                                        break;
     161
     162                                case 'replaygain_album_gain':
     163                                        $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
     164                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
     165                                        break;
     166
     167                                case 'replaygain_album_peak':
     168                                        $thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
     169                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
     170                                        if ($thisfile_replaygain['album']['peak'] <= 0) {
     171                                                $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
     172                                        }
     173                                        break;
     174
     175                                case 'mp3gain_undo':
     176                                        list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
     177                                        $thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
     178                                        $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
     179                                        $thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
     180                                        break;
     181
     182                                case 'mp3gain_minmax':
     183                                        list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
     184                                        $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
     185                                        $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
     186                                        break;
     187
     188                                case 'mp3gain_album_minmax':
     189                                        list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
     190                                        $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
     191                                        $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
     192                                        break;
     193
     194                                case 'tracknumber':
     195                                        if (is_array($thisfile_ape_items_current['data'])) {
     196                                                foreach ($thisfile_ape_items_current['data'] as $comment) {
     197                                                        $thisfile_ape['comments']['track'][] = $comment;
     198                                                }
     199                                        }
     200                                        break;
     201
     202                                case 'cover art (artist)':
     203                                case 'cover art (back)':
     204                                case 'cover art (band logo)':
     205                                case 'cover art (band)':
     206                                case 'cover art (colored fish)':
     207                                case 'cover art (composer)':
     208                                case 'cover art (conductor)':
     209                                case 'cover art (front)':
     210                                case 'cover art (icon)':
     211                                case 'cover art (illustration)':
     212                                case 'cover art (lead)':
     213                                case 'cover art (leaflet)':
     214                                case 'cover art (lyricist)':
     215                                case 'cover art (media)':
     216                                case 'cover art (movie scene)':
     217                                case 'cover art (other icon)':
     218                                case 'cover art (other)':
     219                                case 'cover art (performance)':
     220                                case 'cover art (publisher logo)':
     221                                case 'cover art (recording)':
     222                                case 'cover art (studio)':
     223                                        // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
     224                                        list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
     225                                        $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
     226                                        $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
     227
     228                                        $thisfile_ape_items_current['image_mime'] = '';
     229                                        $imageinfo = array();
     230                                        $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
     231                                        $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
     232
     233                                        do {
     234                                                if ($this->inline_attachments === false) {
     235                                                        // skip entirely
     236                                                        unset($thisfile_ape_items_current['data']);
     237                                                        break;
     238                                                }
     239                                                if ($this->inline_attachments === true) {
     240                                                        // great
     241                                                } elseif (is_int($this->inline_attachments)) {
     242                                                        if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
     243                                                                // too big, skip
     244                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
     245                                                                unset($thisfile_ape_items_current['data']);
     246                                                                break;
     247                                                        }
     248                                                } elseif (is_string($this->inline_attachments)) {
     249                                                        $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
     250                                                        if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
     251                                                                // cannot write, skip
     252                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
     253                                                                unset($thisfile_ape_items_current['data']);
     254                                                                break;
     255                                                        }
     256                                                }
     257                                                // if we get this far, must be OK
     258                                                if (is_string($this->inline_attachments)) {
     259                                                        $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
     260                                                        if (!file_exists($destination_filename) || is_writable($destination_filename)) {
     261                                                                file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
     262                                                        } else {
     263                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
     264                                                        }
     265                                                        $thisfile_ape_items_current['data_filename'] = $destination_filename;
     266                                                        unset($thisfile_ape_items_current['data']);
     267                                                } else {
     268                                                        if (!isset($info['ape']['comments']['picture'])) {
     269                                                                $info['ape']['comments']['picture'] = array();
     270                                                        }
     271                                                        $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
     272                                                }
     273                                        } while (false);
     274                                        break;
     275
     276                                default:
     277                                        if (is_array($thisfile_ape_items_current['data'])) {
     278                                                foreach ($thisfile_ape_items_current['data'] as $comment) {
     279                                                        $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
     280                                                }
     281                                        }
     282                                        break;
     283                        }
     284
     285                }
     286                if (empty($thisfile_replaygain)) {
     287                        unset($info['replay_gain']);
     288                }
     289                return true;
     290        }
     291
     292        public function parseAPEheaderFooter($APEheaderFooterData) {
     293                // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
     294
     295                // shortcut
     296                $headerfooterinfo['raw'] = array();
     297                $headerfooterinfo_raw = &$headerfooterinfo['raw'];
     298
     299                $headerfooterinfo_raw['footer_tag']   =                  substr($APEheaderFooterData,  0, 8);
     300                if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
     301                        return false;
     302                }
     303                $headerfooterinfo_raw['version']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData,  8, 4));
     304                $headerfooterinfo_raw['tagsize']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
     305                $headerfooterinfo_raw['tag_items']    = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
     306                $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
     307                $headerfooterinfo_raw['reserved']     =                              substr($APEheaderFooterData, 24, 8);
     308
     309                $headerfooterinfo['tag_version']         = $headerfooterinfo_raw['version'] / 1000;
     310                if ($headerfooterinfo['tag_version'] >= 2) {
     311                        $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
     312                }
     313                return $headerfooterinfo;
     314        }
     315
     316        public function parseAPEtagFlags($rawflagint) {
     317                // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
     318                // All are set to zero on creation and ignored on reading."
     319                // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
     320                $flags['header']            = (bool) ($rawflagint & 0x80000000);
     321                $flags['footer']            = (bool) ($rawflagint & 0x40000000);
     322                $flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
     323                $flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
     324                $flags['read_only']         = (bool) ($rawflagint & 0x00000001);
     325
     326                $flags['item_contents']     = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
     327
     328                return $flags;
     329        }
     330
     331        public function APEcontentTypeFlagLookup($contenttypeid) {
     332                static $APEcontentTypeFlagLookup = array(
     333                        0 => 'utf-8',
     334                        1 => 'binary',
     335                        2 => 'external',
     336                        3 => 'reserved'
     337                );
     338                return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
     339        }
     340
     341        public function APEtagItemIsUTF8Lookup($itemkey) {
     342                static $APEtagItemIsUTF8Lookup = array(
     343                        'title',
     344                        'subtitle',
     345                        'artist',
     346                        'album',
     347                        'debut album',
     348                        'publisher',
     349                        'conductor',
     350                        'track',
     351                        'composer',
     352                        'comment',
     353                        'copyright',
     354                        'publicationright',
     355                        'file',
     356                        'year',
     357                        'record date',
     358                        'record location',
     359                        'genre',
     360                        'media',
     361                        'related',
     362                        'isrc',
     363                        'abstract',
     364                        'language',
     365                        'bibliography'
     366                );
     367                return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
     368        }
     369
     370}
  • new file wp-includes/ID3/module.tag.id3v1.php

    diff --git wp-includes/ID3/module.tag.id3v1.php wp-includes/ID3/module.tag.id3v1.php
    new file mode 100644
    index 0000000..fd9069e
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9//                                                             //
     10// module.tag.id3v1.php                                        //
     11// module for analyzing ID3v1 tags                             //
     12// dependencies: NONE                                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16
     17class getid3_id3v1 extends getid3_handler
     18{
     19
     20        public function Analyze() {
     21                $info = &$this->getid3->info;
     22
     23                if (!getid3_lib::intValueSupported($info['filesize'])) {
     24                        $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
     25                        return false;
     26                }
     27
     28                fseek($this->getid3->fp, -256, SEEK_END);
     29                $preid3v1 = fread($this->getid3->fp, 128);
     30                $id3v1tag = fread($this->getid3->fp, 128);
     31
     32                if (substr($id3v1tag, 0, 3) == 'TAG') {
     33
     34                        $info['avdataend'] = $info['filesize'] - 128;
     35
     36                        $ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
     37                        $ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
     38                        $ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
     39                        $ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
     40                        $ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
     41                        $ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
     42
     43                        // If second-last byte of comment field is null and last byte of comment field is non-null
     44                        // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
     45                        if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
     46                                $ParsedID3v1['track']   = ord(substr($ParsedID3v1['comment'], 29,  1));
     47                                $ParsedID3v1['comment'] =     substr($ParsedID3v1['comment'],  0, 28);
     48                        }
     49                        $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
     50
     51                        $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
     52                        if (!empty($ParsedID3v1['genre'])) {
     53                                unset($ParsedID3v1['genreid']);
     54                        }
     55                        if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
     56                                unset($ParsedID3v1['genre']);
     57                        }
     58
     59                        foreach ($ParsedID3v1 as $key => $value) {
     60                                $ParsedID3v1['comments'][$key][0] = $value;
     61                        }
     62
     63                        // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
     64                        $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
     65                                                                                        $ParsedID3v1['title'],
     66                                                                                        $ParsedID3v1['artist'],
     67                                                                                        $ParsedID3v1['album'],
     68                                                                                        $ParsedID3v1['year'],
     69                                                                                        (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
     70                                                                                        $ParsedID3v1['comment'],
     71                                                                                        (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
     72                        $ParsedID3v1['padding_valid'] = true;
     73                        if ($id3v1tag !== $GoodFormatID3v1tag) {
     74                                $ParsedID3v1['padding_valid'] = false;
     75                                $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
     76                        }
     77
     78                        $ParsedID3v1['tag_offset_end']   = $info['filesize'];
     79                        $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
     80
     81                        $info['id3v1'] = $ParsedID3v1;
     82                }
     83
     84                if (substr($preid3v1, 0, 3) == 'TAG') {
     85                        // The way iTunes handles tags is, well, brain-damaged.
     86                        // It completely ignores v1 if ID3v2 is present.
     87                        // This goes as far as adding a new v1 tag *even if there already is one*
     88
     89                        // A suspected double-ID3v1 tag has been detected, but it could be that
     90                        // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
     91                        if (substr($preid3v1, 96, 8) == 'APETAGEX') {
     92                                // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
     93                        } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
     94                                // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
     95                        } else {
     96                                // APE and Lyrics3 footers not found - assume double ID3v1
     97                                $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
     98                                $info['avdataend'] -= 128;
     99                        }
     100                }
     101
     102                return true;
     103        }
     104
     105        public static function cutfield($str) {
     106                return trim(substr($str, 0, strcspn($str, "\x00")));
     107        }
     108
     109        public static function ArrayOfGenres($allowSCMPXextended=false) {
     110                static $GenreLookup = array(
     111                        0    => 'Blues',
     112                        1    => 'Classic Rock',
     113                        2    => 'Country',
     114                        3    => 'Dance',
     115                        4    => 'Disco',
     116                        5    => 'Funk',
     117                        6    => 'Grunge',
     118                        7    => 'Hip-Hop',
     119                        8    => 'Jazz',
     120                        9    => 'Metal',
     121                        10   => 'New Age',
     122                        11   => 'Oldies',
     123                        12   => 'Other',
     124                        13   => 'Pop',
     125                        14   => 'R&B',
     126                        15   => 'Rap',
     127                        16   => 'Reggae',
     128                        17   => 'Rock',
     129                        18   => 'Techno',
     130                        19   => 'Industrial',
     131                        20   => 'Alternative',
     132                        21   => 'Ska',
     133                        22   => 'Death Metal',
     134                        23   => 'Pranks',
     135                        24   => 'Soundtrack',
     136                        25   => 'Euro-Techno',
     137                        26   => 'Ambient',
     138                        27   => 'Trip-Hop',
     139                        28   => 'Vocal',
     140                        29   => 'Jazz+Funk',
     141                        30   => 'Fusion',
     142                        31   => 'Trance',
     143                        32   => 'Classical',
     144                        33   => 'Instrumental',
     145                        34   => 'Acid',
     146                        35   => 'House',
     147                        36   => 'Game',
     148                        37   => 'Sound Clip',
     149                        38   => 'Gospel',
     150                        39   => 'Noise',
     151                        40   => 'Alt. Rock',
     152                        41   => 'Bass',
     153                        42   => 'Soul',
     154                        43   => 'Punk',
     155                        44   => 'Space',
     156                        45   => 'Meditative',
     157                        46   => 'Instrumental Pop',
     158                        47   => 'Instrumental Rock',
     159                        48   => 'Ethnic',
     160                        49   => 'Gothic',
     161                        50   => 'Darkwave',
     162                        51   => 'Techno-Industrial',
     163                        52   => 'Electronic',
     164                        53   => 'Pop-Folk',
     165                        54   => 'Eurodance',
     166                        55   => 'Dream',
     167                        56   => 'Southern Rock',
     168                        57   => 'Comedy',
     169                        58   => 'Cult',
     170                        59   => 'Gangsta Rap',
     171                        60   => 'Top 40',
     172                        61   => 'Christian Rap',
     173                        62   => 'Pop/Funk',
     174                        63   => 'Jungle',
     175                        64   => 'Native American',
     176                        65   => 'Cabaret',
     177                        66   => 'New Wave',
     178                        67   => 'Psychedelic',
     179                        68   => 'Rave',
     180                        69   => 'Showtunes',
     181                        70   => 'Trailer',
     182                        71   => 'Lo-Fi',
     183                        72   => 'Tribal',
     184                        73   => 'Acid Punk',
     185                        74   => 'Acid Jazz',
     186                        75   => 'Polka',
     187                        76   => 'Retro',
     188                        77   => 'Musical',
     189                        78   => 'Rock & Roll',
     190                        79   => 'Hard Rock',
     191                        80   => 'Folk',
     192                        81   => 'Folk/Rock',
     193                        82   => 'National Folk',
     194                        83   => 'Swing',
     195                        84   => 'Fast-Fusion',
     196                        85   => 'Bebob',
     197                        86   => 'Latin',
     198                        87   => 'Revival',
     199                        88   => 'Celtic',
     200                        89   => 'Bluegrass',
     201                        90   => 'Avantgarde',
     202                        91   => 'Gothic Rock',
     203                        92   => 'Progressive Rock',
     204                        93   => 'Psychedelic Rock',
     205                        94   => 'Symphonic Rock',
     206                        95   => 'Slow Rock',
     207                        96   => 'Big Band',
     208                        97   => 'Chorus',
     209                        98   => 'Easy Listening',
     210                        99   => 'Acoustic',
     211                        100  => 'Humour',
     212                        101  => 'Speech',
     213                        102  => 'Chanson',
     214                        103  => 'Opera',
     215                        104  => 'Chamber Music',
     216                        105  => 'Sonata',
     217                        106  => 'Symphony',
     218                        107  => 'Booty Bass',
     219                        108  => 'Primus',
     220                        109  => 'Porn Groove',
     221                        110  => 'Satire',
     222                        111  => 'Slow Jam',
     223                        112  => 'Club',
     224                        113  => 'Tango',
     225                        114  => 'Samba',
     226                        115  => 'Folklore',
     227                        116  => 'Ballad',
     228                        117  => 'Power Ballad',
     229                        118  => 'Rhythmic Soul',
     230                        119  => 'Freestyle',
     231                        120  => 'Duet',
     232                        121  => 'Punk Rock',
     233                        122  => 'Drum Solo',
     234                        123  => 'A Cappella',
     235                        124  => 'Euro-House',
     236                        125  => 'Dance Hall',
     237                        126  => 'Goa',
     238                        127  => 'Drum & Bass',
     239                        128  => 'Club-House',
     240                        129  => 'Hardcore',
     241                        130  => 'Terror',
     242                        131  => 'Indie',
     243                        132  => 'BritPop',
     244                        133  => 'Negerpunk',
     245                        134  => 'Polsk Punk',
     246                        135  => 'Beat',
     247                        136  => 'Christian Gangsta Rap',
     248                        137  => 'Heavy Metal',
     249                        138  => 'Black Metal',
     250                        139  => 'Crossover',
     251                        140  => 'Contemporary Christian',
     252                        141  => 'Christian Rock',
     253                        142  => 'Merengue',
     254                        143  => 'Salsa',
     255                        144  => 'Thrash Metal',
     256                        145  => 'Anime',
     257                        146  => 'JPop',
     258                        147  => 'Synthpop',
     259
     260                        255  => 'Unknown',
     261
     262                        'CR' => 'Cover',
     263                        'RX' => 'Remix'
     264                );
     265
     266                static $GenreLookupSCMPX = array();
     267                if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
     268                        $GenreLookupSCMPX = $GenreLookup;
     269                        // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
     270                        // Extended ID3v1 genres invented by SCMPX
     271                        // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
     272                        $GenreLookupSCMPX[240] = 'Sacred';
     273                        $GenreLookupSCMPX[241] = 'Northern Europe';
     274                        $GenreLookupSCMPX[242] = 'Irish & Scottish';
     275                        $GenreLookupSCMPX[243] = 'Scotland';
     276                        $GenreLookupSCMPX[244] = 'Ethnic Europe';
     277                        $GenreLookupSCMPX[245] = 'Enka';
     278                        $GenreLookupSCMPX[246] = 'Children\'s Song';
     279                        $GenreLookupSCMPX[247] = 'Japanese Sky';
     280                        $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
     281                        $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
     282                        $GenreLookupSCMPX[250] = 'Japanese J-POP';
     283                        $GenreLookupSCMPX[251] = 'Japanese Seiyu';
     284                        $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
     285                        $GenreLookupSCMPX[253] = 'Japanese Moemoe';
     286                        $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
     287                        //$GenreLookupSCMPX[255] = 'Japanese Anime';
     288                }
     289
     290                return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
     291        }
     292
     293        public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
     294                switch ($genreid) {
     295                        case 'RX':
     296                        case 'CR':
     297                                break;
     298                        default:
     299                                if (!is_numeric($genreid)) {
     300                                        return false;
     301                                }
     302                                $genreid = intval($genreid); // to handle 3 or '3' or '03'
     303                                break;
     304                }
     305                $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
     306                return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
     307        }
     308
     309        public static function LookupGenreID($genre, $allowSCMPXextended=false) {
     310                $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
     311                $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
     312                foreach ($GenreLookup as $key => $value) {
     313                        if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
     314                                return $key;
     315                        }
     316                }
     317                return false;
     318        }
     319
     320        public static function StandardiseID3v1GenreName($OriginalGenre) {
     321                if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
     322                        return self::LookupGenreName($GenreID);
     323                }
     324                return $OriginalGenre;
     325        }
     326
     327        public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
     328                $ID3v1Tag  = 'TAG';
     329                $ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
     330                $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
     331                $ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
     332                $ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
     333                if (!empty($track) && ($track > 0) && ($track <= 255)) {
     334                        $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
     335                        $ID3v1Tag .= "\x00";
     336                        if (gettype($track) == 'string') {
     337                                $track = (int) $track;
     338                        }
     339                        $ID3v1Tag .= chr($track);
     340                } else {
     341                        $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
     342                }
     343                if (($genreid < 0) || ($genreid > 147)) {
     344                        $genreid = 255; // 'unknown' genre
     345                }
     346                switch (gettype($genreid)) {
     347                        case 'string':
     348                        case 'integer':
     349                                $ID3v1Tag .= chr(intval($genreid));
     350                                break;
     351                        default:
     352                                $ID3v1Tag .= chr(255); // 'unknown' genre
     353                                break;
     354                }
     355
     356                return $ID3v1Tag;
     357        }
     358
     359}
  • new file wp-includes/ID3/module.tag.id3v2.php

    diff --git wp-includes/ID3/module.tag.id3v2.php wp-includes/ID3/module.tag.id3v2.php
    new file mode 100644
    index 0000000..23784e9
    - +  
     1<?php
     2/////////////////////////////////////////////////////////////////
     3/// getID3() by James Heinrich <info@getid3.org>               //
     4//  available at http://getid3.sourceforge.net                 //
     5//            or http://www.getid3.org                         //
     6/////////////////////////////////////////////////////////////////
     7// See readme.txt for more details                             //
     8/////////////////////////////////////////////////////////////////
     9///                                                            //
     10// module.tag.id3v2.php                                        //
     11// module for analyzing ID3v2 tags                             //
     12// dependencies: module.tag.id3v1.php                          //
     13//                                                            ///
     14/////////////////////////////////////////////////////////////////
     15
     16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
     17
     18class getid3_id3v2 extends getid3_handler
     19{
     20        public $StartingOffset = 0;
     21
     22        public function Analyze() {
     23                $info = &$this->getid3->info;
     24
     25                //    Overall tag structure:
     26                //        +-----------------------------+
     27                //        |      Header (10 bytes)      |
     28                //        +-----------------------------+
     29                //        |       Extended Header       |
     30                //        | (variable length, OPTIONAL) |
     31                //        +-----------------------------+
     32                //        |   Frames (variable length)  |
     33                //        +-----------------------------+
     34                //        |           Padding           |
     35                //        | (variable length, OPTIONAL) |
     36                //        +-----------------------------+
     37                //        | Footer (10 bytes, OPTIONAL) |
     38                //        +-----------------------------+
     39
     40                //    Header
     41                //        ID3v2/file identifier      "ID3"
     42                //        ID3v2 version              $04 00
     43                //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
     44                //        ID3v2 size             4 * %0xxxxxxx
     45
     46
     47                // shortcuts
     48                $info['id3v2']['header'] = true;
     49                $thisfile_id3v2                  = &$info['id3v2'];
     50                $thisfile_id3v2['flags']         =  array();
     51                $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
     52
     53
     54                fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET);
     55                $header = fread($this->getid3->fp, 10);
     56                if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
     57
     58                        $thisfile_id3v2['majorversion'] = ord($header{3});
     59                        $thisfile_id3v2['minorversion'] = ord($header{4});
     60
     61                        // shortcut
     62                        $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
     63
     64                } else {
     65
     66                        unset($info['id3v2']);
     67                        return false;
     68
     69                }
     70
     71                if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
     72
     73                        $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
     74                        return false;
     75
     76                }
     77
     78                $id3_flags = ord($header{5});
     79                switch ($id3v2_majorversion) {
     80                        case 2:
     81                                // %ab000000 in v2.2
     82                                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
     83                                $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
     84                                break;
     85
     86                        case 3:
     87                                // %abc00000 in v2.3
     88                                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
     89                                $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
     90                                $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
     91                                break;
     92
     93                        case 4:
     94                                // %abcd0000 in v2.4
     95                                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
     96                                $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
     97                                $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
     98                                $thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
     99                                break;
     100                }
     101
     102                $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
     103
     104                $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
     105                $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
     106
     107
     108
     109                // create 'encoding' key - used by getid3::HandleAllTags()
     110                // in ID3v2 every field can have it's own encoding type
     111                // so force everything to UTF-8 so it can be handled consistantly
     112                $thisfile_id3v2['encoding'] = 'UTF-8';
     113
     114
     115        //    Frames
     116
     117        //        All ID3v2 frames consists of one frame header followed by one or more
     118        //        fields containing the actual information. The header is always 10
     119        //        bytes and laid out as follows:
     120        //
     121        //        Frame ID      $xx xx xx xx  (four characters)
     122        //        Size      4 * %0xxxxxxx
     123        //        Flags         $xx xx
     124
     125                $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
     126                if (!empty($thisfile_id3v2['exthead']['length'])) {
     127                        $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
     128                }
     129                if (!empty($thisfile_id3v2_flags['isfooter'])) {
     130                        $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
     131                }
     132                if ($sizeofframes > 0) {
     133
     134                        $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable
     135
     136                        //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
     137                        if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
     138                                $framedata = $this->DeUnsynchronise($framedata);
     139                        }
     140                        //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
     141                        //        of on tag level, making it easier to skip frames, increasing the streamability
     142                        //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
     143                        //        there exists an unsynchronised frame, while the new unsynchronisation flag in
     144                        //        the frame header [S:4.1.2] indicates unsynchronisation.
     145
     146
     147                        //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
     148                        $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
     149
     150
     151                        //    Extended Header
     152                        if (!empty($thisfile_id3v2_flags['exthead'])) {
     153                                $extended_header_offset = 0;
     154
     155                                if ($id3v2_majorversion == 3) {
     156
     157                                        // v2.3 definition:
     158                                        //Extended header size  $xx xx xx xx   // 32-bit integer
     159                                        //Extended Flags        $xx xx
     160                                        //     %x0000000 %00000000 // v2.3
     161                                        //     x - CRC data present
     162                                        //Size of padding       $xx xx xx xx
     163
     164                                        $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
     165                                        $extended_header_offset += 4;
     166
     167                                        $thisfile_id3v2['exthead']['flag_bytes'] = 2;
     168                                        $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
     169                                        $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
     170
     171                                        $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
     172
     173                                        $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
     174                                        $extended_header_offset += 4;
     175
     176                                        if ($thisfile_id3v2['exthead']['flags']['crc']) {
     177                                                $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
     178                                                $extended_header_offset += 4;
     179                                        }
     180                                        $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
     181
     182                                } elseif ($id3v2_majorversion == 4) {
     183
     184                                        // v2.4 definition:
     185                                        //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
     186                                        //Number of flag bytes       $01
     187                                        //Extended Flags             $xx
     188                                        //     %0bcd0000 // v2.4
     189                                        //     b - Tag is an update
     190                                        //         Flag data length       $00
     191                                        //     c - CRC data present
     192                                        //         Flag data length       $05
     193                                        //         Total frame CRC    5 * %0xxxxxxx
     194                                        //     d - Tag restrictions
     195                                        //         Flag data length       $01
     196
     197                                        $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
     198                                        $extended_header_offset += 4;
     199
     200                                        $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
     201                                        $extended_header_offset += 1;
     202
     203                                        $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
     204                                        $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
     205
     206                                        $thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
     207                                        $thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
     208                                        $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
     209
     210                                        if ($thisfile_id3v2['exthead']['flags']['update']) {
     211                                                $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
     212                                                $extended_header_offset += 1;
     213                                        }
     214
     215                                        if ($thisfile_id3v2['exthead']['flags']['crc']) {
     216                                                $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
     217                                                $extended_header_offset += 1;
     218                                                $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
     219                                                $extended_header_offset += $ext_header_chunk_length;
     220                                        }
     221
     222                                        if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
     223                                                $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
     224                                                $extended_header_offset += 1;
     225
     226                                                // %ppqrrstt
     227                                                $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
     228                                                $extended_header_offset += 1;
     229                                                $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
     230                                                $thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
     231                                                $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
     232                                                $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
     233                                                $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
     234
     235                                                $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
     236                                                $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
     237                                                $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
     238                                                $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
     239                                                $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
     240                                        }
     241
     242                                        if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
     243                                                $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
     244                                        }
     245                                }
     246
     247                                $framedataoffset += $extended_header_offset;
     248                                $framedata = substr($framedata, $extended_header_offset);
     249                        } // end extended header
     250
     251
     252                        while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
     253                                if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
     254                                        // insufficient room left in ID3v2 header for actual data - must be padding
     255                                        $thisfile_id3v2['padding']['start']  = $framedataoffset;
     256                                        $thisfile_id3v2['padding']['length'] = strlen($framedata);
     257                                        $thisfile_id3v2['padding']['valid']  = true;
     258                                        for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
     259                                                if ($framedata{$i} != "\x00") {
     260                                                        $thisfile_id3v2['padding']['valid'] = false;
     261                                                        $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
     262                                                        $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
     263                                                        break;
     264                                                }
     265                                        }
     266                                        break; // skip rest of ID3v2 header
     267                                }
     268                                if ($id3v2_majorversion == 2) {
     269                                        // Frame ID  $xx xx xx (three characters)
     270                                        // Size      $xx xx xx (24-bit integer)
     271                                        // Flags     $xx xx
     272
     273                                        $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
     274                                        $framedata    = substr($framedata, 6);    // and leave the rest in $framedata
     275                                        $frame_name   = substr($frame_header, 0, 3);
     276                                        $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
     277                                        $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
     278
     279                                } elseif ($id3v2_majorversion > 2) {
     280
     281                                        // Frame ID  $xx xx xx xx (four characters)
     282                                        // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
     283                                        // Flags     $xx xx
     284
     285                                        $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
     286                                        $framedata    = substr($framedata, 10);    // and leave the rest in $framedata
     287
     288                                        $frame_name = substr($frame_header, 0, 4);
     289                                        if ($id3v2_majorversion == 3) {
     290                                                $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
     291                                        } else { // ID3v2.4+
     292                                                $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
     293                                        }
     294
     295                                        if ($frame_size < (strlen($framedata) + 4)) {
     296                                                $nextFrameID = substr($framedata, $frame_size, 4);
     297                                                if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
     298                                                        // next frame is OK
     299                                                } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
     300                                                        // MP3ext known broken frames - "ok" for the purposes of this test
     301                                                } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
     302                                                        $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
     303                                                        $id3v2_majorversion = 3;
     304                                                        $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
     305                                                }
     306                                        }
     307
     308
     309                                        $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
     310                                }
     311
     312                                if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
     313                                        // padding encountered
     314
     315                                        $thisfile_id3v2['padding']['start']  = $framedataoffset;
     316                                        $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
     317                                        $thisfile_id3v2['padding']['valid']  = true;
     318
     319                                        $len = strlen($framedata);
     320                                        for ($i = 0; $i < $len; $i++) {
     321                                                if ($framedata{$i} != "\x00") {
     322                                                        $thisfile_id3v2['padding']['valid'] = false;
     323                                                        $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
     324                                                        $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
     325                                                        break;
     326                                                }
     327                                        }
     328                                        break; // skip rest of ID3v2 header
     329                                }
     330
     331                                if ($frame_name == 'COM ') {
     332                                        $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
     333                                        $frame_name = 'COMM';
     334                                }
     335                                if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
     336
     337                                        unset($parsedFrame);
     338                                        $parsedFrame['frame_name']      = $frame_name;
     339                                        $parsedFrame['frame_flags_raw'] = $frame_flags;
     340                                        $parsedFrame['data']            = substr($framedata, 0, $frame_size);
     341                                        $parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
     342                                        $parsedFrame['dataoffset']      = $framedataoffset;
     343
     344                                        $this->ParseID3v2Frame($parsedFrame);
     345                                        $thisfile_id3v2[$frame_name][] = $parsedFrame;
     346
     347                                        $framedata = substr($framedata, $frame_size);
     348
     349                                } else { // invalid frame length or FrameID
     350
     351                                        if ($frame_size <= strlen($framedata)) {
     352
     353                                                if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
     354
     355                                                        // next frame is valid, just skip the current frame
     356                                                        $framedata = substr($framedata, $frame_size);
     357                                                        $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
     358
     359                                                } else {
     360
     361                                                        // next frame is invalid too, abort processing
     362                                                        //unset($framedata);
     363                                                        $framedata = null;
     364                                                        $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
     365
     366                                                }
     367
     368                                        } elseif ($frame_size == strlen($framedata)) {
     369
     370                                                // this is the last frame, just skip
     371                                                $info['warning'][] = 'This was the last ID3v2 frame.';
     372
     373                                        } else {
     374
     375                                                // next frame is invalid too, abort processing
     376                                                //unset($framedata);
     377                                                $framedata = null;
     378                                                $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
     379
     380                                        }
     381                                        if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
     382
     383                                                switch ($frame_name) {
     384                                                        case "\x00\x00".'MP':
     385                                                        case "\x00".'MP3':
     386                                                        case ' MP3':
     387                                                        case 'MP3e':
     388                                                        case "\x00".'MP':
     389                                                        case ' MP':
     390                                                        case 'MP3':
     391                                                                $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
     392                                                                break;
     393
     394                                                        default:
     395                                                                $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
     396                                                                break;
     397                                                }
     398
     399                                        } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
     400
     401                                                $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
     402
     403                                        } else {
     404
     405                                                $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
     406
     407                                        }
     408
     409                                }
     410                                $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
     411
     412                        }
     413
     414                }
     415
     416
     417        //    Footer
     418
     419        //    The footer is a copy of the header, but with a different identifier.
     420        //        ID3v2 identifier           "3DI"
     421        //        ID3v2 version              $04 00
     422        //        ID3v2 flags                %abcd0000
     423        //        ID3v2 size             4 * %0xxxxxxx
     424
     425                if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
     426                        $footer = fread($this->getid3->fp, 10);
     427                        if (substr($footer, 0, 3) == '3DI') {
     428                                $thisfile_id3v2['footer'] = true;
     429                                $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
     430                                $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
     431                        }
     432                        if ($thisfile_id3v2['majorversion_footer'] <= 4) {
     433                                $id3_flags = ord(substr($footer{5}));
     434                                $thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
     435                                $thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
     436                                $thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
     437                                $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
     438
     439                                $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
     440                        }
     441                } // end footer
     442
     443                if (isset($thisfile_id3v2['comments']['genre'])) {
     444                        foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
     445                                unset($thisfile_id3v2['comments']['genre'][$key]);
     446                                $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
     447                        }
     448                }
     449
     450                if (isset($thisfile_id3v2['comments']['track'])) {
     451                        foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
     452                                if (strstr($value, '/')) {
     453                                        list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
     454                                }
     455                        }
     456                }
     457
     458                if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
     459                        $thisfile_id3v2['comments']['year'] = array($matches[1]);
     460                }
     461
     462
     463                if (!empty($thisfile_id3v2['TXXX'])) {
     464                        // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
     465                        foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
     466                                switch ($txxx_array['description']) {
     467                                        case 'replaygain_track_gain':
     468                                                if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
     469                                                        $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
     470                                                }
     471                                                break;
     472                                        case 'replaygain_track_peak':
     473                                                if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
     474                                                        $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
     475                                                }
     476                                                break;
     477                                        case 'replaygain_album_gain':
     478                                                if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
     479                                                        $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
     480                                                }
     481                                                break;
     482                                }
     483                        }
     484                }
     485
     486
     487                // Set avdataoffset
     488                $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
     489                if (isset($thisfile_id3v2['footer'])) {
     490                        $info['avdataoffset'] += 10;
     491                }
     492
     493                return true;
     494        }
     495
     496
     497        public function ParseID3v2GenreString($genrestring) {
     498                // Parse genres into arrays of genreName and genreID
     499                // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
     500                // ID3v2.4.x: '21' $00 'Eurodisco' $00
     501                $clean_genres = array();
     502                if (strpos($genrestring, "\x00") === false) {
     503                        $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
     504                }
     505                $genre_elements = explode("\x00", $genrestring);
     506                foreach ($genre_elements as $element) {
     507                        $element = trim($element);
     508                        if ($element) {
     509                                if (preg_match('#^[0-9]{1,3}#', $element)) {
     510                                        $clean_genres[] = getid3_id3v1::LookupGenreName($element);
     511                                } else {
     512                                        $clean_genres[] = str_replace('((', '(', $element);
     513                                }
     514                        }
     515                }
     516                return $clean_genres;
     517        }
     518
     519
     520        public function ParseID3v2Frame(&$parsedFrame) {
     521
     522                // shortcuts
     523                $info = &$this->getid3->info;
     524                $id3v2_majorversion = $info['id3v2']['majorversion'];
     525
     526                $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
     527                if (empty($parsedFrame['framenamelong'])) {
     528                        unset($parsedFrame['framenamelong']);
     529                }
     530                $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
     531                if (empty($parsedFrame['framenameshort'])) {
     532                        unset($parsedFrame['framenameshort']);
     533                }
     534
     535                if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
     536                        if ($id3v2_majorversion == 3) {
     537                                //    Frame Header Flags
     538                                //    %abc00000 %ijk00000
     539                                $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
     540                                $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
     541                                $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
     542                                $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
     543                                $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
     544                                $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
     545
     546                        } elseif ($id3v2_majorversion == 4) {
     547                                //    Frame Header Flags
     548                                //    %0abc0000 %0h00kmnp
     549                                $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
     550                                $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
     551                                $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
     552                                $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
     553                                $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
     554                                $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
     555                                $parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
     556                                $parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
     557
     558                                // Frame-level de-unsynchronisation - ID3v2.4
     559                                if ($parsedFrame['flags']['Unsynchronisation']) {
     560                                        $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
     561                                }
     562
     563                                if ($parsedFrame['flags']['DataLengthIndicator']) {
     564                                        $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
     565                                        $parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
     566                                }
     567                        }
     568
     569                        //    Frame-level de-compression
     570                        if ($parsedFrame['flags']['compression']) {
     571                                $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
     572                                if (!function_exists('gzuncompress')) {
     573                                        $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
     574                                } else {
     575                                        if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
     576                                        //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
     577                                                $parsedFrame['data'] = $decompresseddata;
     578                                                unset($decompresseddata);
     579                                        } else {
     580                                                $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
     581                                        }
     582                                }
     583                        }
     584                }
     585
     586                if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
     587                        if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
     588                                $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
     589                        }
     590                }
     591
     592                if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
     593
     594                        $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
     595                        switch ($parsedFrame['frame_name']) {
     596                                case 'WCOM':
     597                                        $warning .= ' (this is known to happen with files tagged by RioPort)';
     598                                        break;
     599
     600                                default:
     601                                        break;
     602                        }
     603                        $info['warning'][] = $warning;
     604
     605                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
     606                        (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
     607                        //   There may be more than one 'UFID' frame in a tag,
     608                        //   but only one with the same 'Owner identifier'.
     609                        // <Header for 'Unique file identifier', ID: 'UFID'>
     610                        // Owner identifier        <text string> $00
     611                        // Identifier              <up to 64 bytes binary data>
     612                        $exploded = explode("\x00", $parsedFrame['data'], 2);
     613                        $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
     614                        $parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
     615
     616                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
     617                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
     618                        //   There may be more than one 'TXXX' frame in each tag,
     619                        //   but only one with the same description.
     620                        // <Header for 'User defined text information frame', ID: 'TXXX'>
     621                        // Text encoding     $xx
     622                        // Description       <text string according to encoding> $00 (00)
     623                        // Value             <text string according to encoding>
     624
     625                        $frame_offset = 0;
     626                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     627
     628                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     629                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     630                        }
     631                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     632                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     633                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     634                        }
     635                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     636                        if (ord($frame_description) === 0) {
     637                                $frame_description = '';
     638                        }
     639                        $parsedFrame['encodingid']  = $frame_textencoding;
     640                        $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
     641
     642                        $parsedFrame['description'] = $frame_description;
     643                        $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     644                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     645                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
     646                        }
     647                        //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
     648
     649
     650                } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
     651                        //   There may only be one text information frame of its kind in an tag.
     652                        // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
     653                        // excluding 'TXXX' described in 4.2.6.>
     654                        // Text encoding                $xx
     655                        // Information                  <text string(s) according to encoding>
     656
     657                        $frame_offset = 0;
     658                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     659                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     660                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     661                        }
     662
     663                        $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
     664
     665                        $parsedFrame['encodingid'] = $frame_textencoding;
     666                        $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
     667
     668                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     669                                // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
     670                                // This of course breaks when an aritst name contains slash character, e.g. "AC/DC"
     671                                // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
     672                                // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
     673                                switch ($parsedFrame['encoding']) {
     674                                        case 'UTF-16':
     675                                        case 'UTF-16BE':
     676                                        case 'UTF-16LE':
     677                                                $wordsize = 2;
     678                                                break;
     679                                        case 'ISO-8859-1':
     680                                        case 'UTF-8':
     681                                        default:
     682                                                $wordsize = 1;
     683                                                break;
     684                                }
     685                                $Txxx_elements = array();
     686                                $Txxx_elements_start_offset = 0;
     687                                for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
     688                                        if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
     689                                                $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
     690                                                $Txxx_elements_start_offset = $i + $wordsize;
     691                                        }
     692                                }
     693                                $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
     694                                foreach ($Txxx_elements as $Txxx_element) {
     695                                        $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
     696                                        if (!empty($string)) {
     697                                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
     698                                        }
     699                                }
     700                                unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
     701                        }
     702
     703                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
     704                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
     705                        //   There may be more than one 'WXXX' frame in each tag,
     706                        //   but only one with the same description
     707                        // <Header for 'User defined URL link frame', ID: 'WXXX'>
     708                        // Text encoding     $xx
     709                        // Description       <text string according to encoding> $00 (00)
     710                        // URL               <text string>
     711
     712                        $frame_offset = 0;
     713                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     714                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     715                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     716                        }
     717                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     718                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     719                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     720                        }
     721                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     722
     723                        if (ord($frame_description) === 0) {
     724                                $frame_description = '';
     725                        }
     726                        $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     727
     728                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
     729                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     730                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     731                        }
     732                        if ($frame_terminatorpos) {
     733                                // there are null bytes after the data - this is not according to spec
     734                                // only use data up to first null byte
     735                                $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
     736                        } else {
     737                                // no null bytes following data, just use all data
     738                                $frame_urldata = (string) $parsedFrame['data'];
     739                        }
     740
     741                        $parsedFrame['encodingid']  = $frame_textencoding;
     742                        $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
     743
     744                        $parsedFrame['url']         = $frame_urldata;
     745                        $parsedFrame['description'] = $frame_description;
     746                        if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
     747                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
     748                        }
     749                        unset($parsedFrame['data']);
     750
     751
     752                } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
     753                        //   There may only be one URL link frame of its kind in a tag,
     754                        //   except when stated otherwise in the frame description
     755                        // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
     756                        // described in 4.3.2.>
     757                        // URL              <text string>
     758
     759                        $parsedFrame['url'] = trim($parsedFrame['data']);
     760                        if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
     761                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
     762                        }
     763                        unset($parsedFrame['data']);
     764
     765
     766                } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
     767                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
     768                        // http://id3.org/id3v2.3.0#sec4.4
     769                        //   There may only be one 'IPL' frame in each tag
     770                        // <Header for 'User defined URL link frame', ID: 'IPL'>
     771                        // Text encoding     $xx
     772                        // People list strings    <textstrings>
     773
     774                        $frame_offset = 0;
     775                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     776                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     777                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     778                        }
     779                        $parsedFrame['encodingid'] = $frame_textencoding;
     780                        $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
     781                        $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
     782
     783                        // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
     784                        // "this tag typically contains null terminated strings, which are associated in pairs"
     785                        // "there are users that use the tag incorrectly"
     786                        $IPLS_parts = array();
     787                        if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
     788                                $IPLS_parts_unsorted = array();
     789                                if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
     790                                        // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
     791                                        $thisILPS  = '';
     792                                        for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
     793                                                $twobytes = substr($parsedFrame['data_raw'], $i, 2);
     794                                                if ($twobytes === "\x00\x00") {
     795                                                        $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
     796                                                        $thisILPS  = '';
     797                                                } else {
     798                                                        $thisILPS .= $twobytes;
     799                                                }
     800                                        }
     801                                        if (strlen($thisILPS) > 2) { // 2-byte BOM
     802                                                $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
     803                                        }
     804                                } else {
     805                                        // ISO-8859-1 or UTF-8 or other single-byte-null character set
     806                                        $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
     807                                }
     808                                if (count($IPLS_parts_unsorted) == 1) {
     809                                        // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
     810                                        foreach ($IPLS_parts_unsorted as $key => $value) {
     811                                                $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
     812                                                $position = '';
     813                                                foreach ($IPLS_parts_sorted as $person) {
     814                                                        $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
     815                                                }
     816                                        }
     817                                } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
     818                                        $position = '';
     819                                        $person   = '';
     820                                        foreach ($IPLS_parts_unsorted as $key => $value) {
     821                                                if (($key % 2) == 0) {
     822                                                        $position = $value;
     823                                                } else {
     824                                                        $person   = $value;
     825                                                        $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
     826                                                        $position = '';
     827                                                        $person   = '';
     828                                                }
     829                                        }
     830                                } else {
     831                                        foreach ($IPLS_parts_unsorted as $key => $value) {
     832                                                $IPLS_parts[] = array($value);
     833                                        }
     834                                }
     835
     836                        } else {
     837                                $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
     838                        }
     839                        $parsedFrame['data'] = $IPLS_parts;
     840
     841                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     842                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
     843                        }
     844
     845
     846                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
     847                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
     848                        //   There may only be one 'MCDI' frame in each tag
     849                        // <Header for 'Music CD identifier', ID: 'MCDI'>
     850                        // CD TOC                <binary data>
     851
     852                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     853                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
     854                        }
     855
     856
     857                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
     858                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
     859                        //   There may only be one 'ETCO' frame in each tag
     860                        // <Header for 'Event timing codes', ID: 'ETCO'>
     861                        // Time stamp format    $xx
     862                        //   Where time stamp format is:
     863                        // $01  (32-bit value) MPEG frames from beginning of file
     864                        // $02  (32-bit value) milliseconds from beginning of file
     865                        //   Followed by a list of key events in the following format:
     866                        // Type of event   $xx
     867                        // Time stamp      $xx (xx ...)
     868                        //   The 'Time stamp' is set to zero if directly at the beginning of the sound
     869                        //   or after the previous event. All events MUST be sorted in chronological order.
     870
     871                        $frame_offset = 0;
     872                        $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     873
     874                        while ($frame_offset < strlen($parsedFrame['data'])) {
     875                                $parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
     876                                $parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
     877                                $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     878                                $frame_offset += 4;
     879                        }
     880                        unset($parsedFrame['data']);
     881
     882
     883                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
     884                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
     885                        //   There may only be one 'MLLT' frame in each tag
     886                        // <Header for 'Location lookup table', ID: 'MLLT'>
     887                        // MPEG frames between reference  $xx xx
     888                        // Bytes between reference        $xx xx xx
     889                        // Milliseconds between reference $xx xx xx
     890                        // Bits for bytes deviation       $xx
     891                        // Bits for milliseconds dev.     $xx
     892                        //   Then for every reference the following data is included;
     893                        // Deviation in bytes         %xxx....
     894                        // Deviation in milliseconds  %xxx....
     895
     896                        $frame_offset = 0;
     897                        $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
     898                        $parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
     899                        $parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
     900                        $parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
     901                        $parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
     902                        $parsedFrame['data'] = substr($parsedFrame['data'], 10);
     903                        while ($frame_offset < strlen($parsedFrame['data'])) {
     904                                $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
     905                        }
     906                        $reference_counter = 0;
     907                        while (strlen($deviationbitstream) > 0) {
     908                                $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
     909                                $parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
     910                                $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
     911                                $reference_counter++;
     912                        }
     913                        unset($parsedFrame['data']);
     914
     915
     916                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
     917                                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
     918                        //   There may only be one 'SYTC' frame in each tag
     919                        // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
     920                        // Time stamp format   $xx
     921                        // Tempo data          <binary data>
     922                        //   Where time stamp format is:
     923                        // $01  (32-bit value) MPEG frames from beginning of file
     924                        // $02  (32-bit value) milliseconds from beginning of file
     925
     926                        $frame_offset = 0;
     927                        $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     928                        $timestamp_counter = 0;
     929                        while ($frame_offset < strlen($parsedFrame['data'])) {
     930                                $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     931                                if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
     932                                        $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
     933                                }
     934                                $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     935                                $frame_offset += 4;
     936                                $timestamp_counter++;
     937                        }
     938                        unset($parsedFrame['data']);
     939
     940
     941                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
     942                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
     943                        //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
     944                        //   in each tag, but only one with the same language and content descriptor.
     945                        // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
     946                        // Text encoding        $xx
     947                        // Language             $xx xx xx
     948                        // Content descriptor   <text string according to encoding> $00 (00)
     949                        // Lyrics/text          <full text string according to encoding>
     950
     951                        $frame_offset = 0;
     952                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     953                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     954                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     955                        }
     956                        $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
     957                        $frame_offset += 3;
     958                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     959                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     960                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     961                        }
     962                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     963                        if (ord($frame_description) === 0) {
     964                                $frame_description = '';
     965                        }
     966                        $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     967
     968                        $parsedFrame['encodingid']   = $frame_textencoding;
     969                        $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
     970
     971                        $parsedFrame['data']         = $parsedFrame['data'];
     972                        $parsedFrame['language']     = $frame_language;
     973                        $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
     974                        $parsedFrame['description']  = $frame_description;
     975                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     976                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
     977                        }
     978                        unset($parsedFrame['data']);
     979
     980
     981                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
     982                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
     983                        //   There may be more than one 'SYLT' frame in each tag,
     984                        //   but only one with the same language and content descriptor.
     985                        // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
     986                        // Text encoding        $xx
     987                        // Language             $xx xx xx
     988                        // Time stamp format    $xx
     989                        //   $01  (32-bit value) MPEG frames from beginning of file
     990                        //   $02  (32-bit value) milliseconds from beginning of file
     991                        // Content type         $xx
     992                        // Content descriptor   <text string according to encoding> $00 (00)
     993                        //   Terminated text to be synced (typically a syllable)
     994                        //   Sync identifier (terminator to above string)   $00 (00)
     995                        //   Time stamp                                     $xx (xx ...)
     996
     997                        $frame_offset = 0;
     998                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     999                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1000                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1001                        }
     1002                        $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
     1003                        $frame_offset += 3;
     1004                        $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1005                        $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1006                        $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
     1007                        $parsedFrame['encodingid']      = $frame_textencoding;
     1008                        $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
     1009
     1010                        $parsedFrame['language']        = $frame_language;
     1011                        $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
     1012
     1013                        $timestampindex = 0;
     1014                        $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
     1015                        while (strlen($frame_remainingdata)) {
     1016                                $frame_offset = 0;
     1017                                $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
     1018                                if ($frame_terminatorpos === false) {
     1019                                        $frame_remainingdata = '';
     1020                                } else {
     1021                                        if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1022                                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1023                                        }
     1024                                        $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
     1025
     1026                                        $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     1027                                        if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
     1028                                                // timestamp probably omitted for first data item
     1029                                        } else {
     1030                                                $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
     1031                                                $frame_remainingdata = substr($frame_remainingdata, 4);
     1032                                        }
     1033                                        $timestampindex++;
     1034                                }
     1035                        }
     1036                        unset($parsedFrame['data']);
     1037
     1038
     1039                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
     1040                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
     1041                        //   There may be more than one comment frame in each tag,
     1042                        //   but only one with the same language and content descriptor.
     1043                        // <Header for 'Comment', ID: 'COMM'>
     1044                        // Text encoding          $xx
     1045                        // Language               $xx xx xx
     1046                        // Short content descrip. <text string according to encoding> $00 (00)
     1047                        // The actual text        <full text string according to encoding>
     1048
     1049                        if (strlen($parsedFrame['data']) < 5) {
     1050
     1051                                $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
     1052
     1053                        } else {
     1054
     1055                                $frame_offset = 0;
     1056                                $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1057                                if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1058                                        $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1059                                }
     1060                                $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
     1061                                $frame_offset += 3;
     1062                                $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1063                                if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1064                                        $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1065                                }
     1066                                $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1067                                if (ord($frame_description) === 0) {
     1068                                        $frame_description = '';
     1069                                }
     1070                                $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     1071
     1072                                $parsedFrame['encodingid']   = $frame_textencoding;
     1073                                $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
     1074
     1075                                $parsedFrame['language']     = $frame_language;
     1076                                $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
     1077                                $parsedFrame['description']  = $frame_description;
     1078                                $parsedFrame['data']         = $frame_text;
     1079                                if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     1080                                        $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
     1081                                }
     1082
     1083                        }
     1084
     1085                } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
     1086                        //   There may be more than one 'RVA2' frame in each tag,
     1087                        //   but only one with the same identification string
     1088                        // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
     1089                        // Identification          <text string> $00
     1090                        //   The 'identification' string is used to identify the situation and/or
     1091                        //   device where this adjustment should apply. The following is then
     1092                        //   repeated for every channel:
     1093                        // Type of channel         $xx
     1094                        // Volume adjustment       $xx xx
     1095                        // Bits representing peak  $xx
     1096                        // Peak volume             $xx (xx ...)
     1097
     1098                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
     1099                        $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
     1100                        if (ord($frame_idstring) === 0) {
     1101                                $frame_idstring = '';
     1102                        }
     1103                        $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
     1104                        $parsedFrame['description'] = $frame_idstring;
     1105                        $RVA2channelcounter = 0;
     1106                        while (strlen($frame_remainingdata) >= 5) {
     1107                                $frame_offset = 0;
     1108                                $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
     1109                                $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
     1110                                $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
     1111                                $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
     1112                                $frame_offset += 2;
     1113                                $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
     1114                                if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
     1115                                        $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
     1116                                        break;
     1117                                }
     1118                                $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
     1119                                $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
     1120                                $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
     1121                                $RVA2channelcounter++;
     1122                        }
     1123                        unset($parsedFrame['data']);
     1124
     1125
     1126                } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
     1127                                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
     1128                        //   There may only be one 'RVA' frame in each tag
     1129                        // <Header for 'Relative volume adjustment', ID: 'RVA'>
     1130                        // ID3v2.2 => Increment/decrement     %000000ba
     1131                        // ID3v2.3 => Increment/decrement     %00fedcba
     1132                        // Bits used for volume descr.        $xx
     1133                        // Relative volume change, right      $xx xx (xx ...) // a
     1134                        // Relative volume change, left       $xx xx (xx ...) // b
     1135                        // Peak volume right                  $xx xx (xx ...)
     1136                        // Peak volume left                   $xx xx (xx ...)
     1137                        //   ID3v2.3 only, optional (not present in ID3v2.2):
     1138                        // Relative volume change, right back $xx xx (xx ...) // c
     1139                        // Relative volume change, left back  $xx xx (xx ...) // d
     1140                        // Peak volume right back             $xx xx (xx ...)
     1141                        // Peak volume left back              $xx xx (xx ...)
     1142                        //   ID3v2.3 only, optional (not present in ID3v2.2):
     1143                        // Relative volume change, center     $xx xx (xx ...) // e
     1144                        // Peak volume center                 $xx xx (xx ...)
     1145                        //   ID3v2.3 only, optional (not present in ID3v2.2):
     1146                        // Relative volume change, bass       $xx xx (xx ...) // f
     1147                        // Peak volume bass                   $xx xx (xx ...)
     1148
     1149                        $frame_offset = 0;
     1150                        $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
     1151                        $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
     1152                        $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
     1153                        $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1154                        $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
     1155                        $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1156                        if ($parsedFrame['incdec']['right'] === false) {
     1157                                $parsedFrame['volumechange']['right'] *= -1;
     1158                        }
     1159                        $frame_offset += $frame_bytesvolume;
     1160                        $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1161                        if ($parsedFrame['incdec']['left'] === false) {
     1162                                $parsedFrame['volumechange']['left'] *= -1;
     1163                        }
     1164                        $frame_offset += $frame_bytesvolume;
     1165                        $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1166                        $frame_offset += $frame_bytesvolume;
     1167                        $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1168                        $frame_offset += $frame_bytesvolume;
     1169                        if ($id3v2_majorversion == 3) {
     1170                                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
     1171                                if (strlen($parsedFrame['data']) > 0) {
     1172                                        $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
     1173                                        $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
     1174                                        $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1175                                        if ($parsedFrame['incdec']['rightrear'] === false) {
     1176                                                $parsedFrame['volumechange']['rightrear'] *= -1;
     1177                                        }
     1178                                        $frame_offset += $frame_bytesvolume;
     1179                                        $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1180                                        if ($parsedFrame['incdec']['leftrear'] === false) {
     1181                                                $parsedFrame['volumechange']['leftrear'] *= -1;
     1182                                        }
     1183                                        $frame_offset += $frame_bytesvolume;
     1184                                        $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1185                                        $frame_offset += $frame_bytesvolume;
     1186                                        $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1187                                        $frame_offset += $frame_bytesvolume;
     1188                                }
     1189                                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
     1190                                if (strlen($parsedFrame['data']) > 0) {
     1191                                        $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
     1192                                        $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1193                                        if ($parsedFrame['incdec']['center'] === false) {
     1194                                                $parsedFrame['volumechange']['center'] *= -1;
     1195                                        }
     1196                                        $frame_offset += $frame_bytesvolume;
     1197                                        $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1198                                        $frame_offset += $frame_bytesvolume;
     1199                                }
     1200                                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
     1201                                if (strlen($parsedFrame['data']) > 0) {
     1202                                        $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
     1203                                        $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1204                                        if ($parsedFrame['incdec']['bass'] === false) {
     1205                                                $parsedFrame['volumechange']['bass'] *= -1;
     1206                                        }
     1207                                        $frame_offset += $frame_bytesvolume;
     1208                                        $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
     1209                                        $frame_offset += $frame_bytesvolume;
     1210                                }
     1211                        }
     1212                        unset($parsedFrame['data']);
     1213
     1214
     1215                } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
     1216                        //   There may be more than one 'EQU2' frame in each tag,
     1217                        //   but only one with the same identification string
     1218                        // <Header of 'Equalisation (2)', ID: 'EQU2'>
     1219                        // Interpolation method  $xx
     1220                        //   $00  Band
     1221                        //   $01  Linear
     1222                        // Identification        <text string> $00
     1223                        //   The following is then repeated for every adjustment point
     1224                        // Frequency          $xx xx
     1225                        // Volume adjustment  $xx xx
     1226
     1227                        $frame_offset = 0;
     1228                        $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1229                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1230                        $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1231                        if (ord($frame_idstring) === 0) {
     1232                                $frame_idstring = '';
     1233                        }
     1234                        $parsedFrame['description'] = $frame_idstring;
     1235                        $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
     1236                        while (strlen($frame_remainingdata)) {
     1237                                $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
     1238                                $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
     1239                                $frame_remainingdata = substr($frame_remainingdata, 4);
     1240                        }
     1241                        $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
     1242                        unset($parsedFrame['data']);
     1243
     1244
     1245                } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
     1246                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
     1247                        //   There may only be one 'EQUA' frame in each tag
     1248                        // <Header for 'Relative volume adjustment', ID: 'EQU'>
     1249                        // Adjustment bits    $xx
     1250                        //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
     1251                        //   nearest byte) for every equalisation band in the following format,
     1252                        //   giving a frequency range of 0 - 32767Hz:
     1253                        // Increment/decrement   %x (MSB of the Frequency)
     1254                        // Frequency             (lower 15 bits)
     1255                        // Adjustment            $xx (xx ...)
     1256
     1257                        $frame_offset = 0;
     1258                        $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
     1259                        $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
     1260
     1261                        $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
     1262                        while (strlen($frame_remainingdata) > 0) {
     1263                                $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
     1264                                $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
     1265                                $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
     1266                                $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
     1267                                $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
     1268                                if ($parsedFrame[$frame_frequency]['incdec'] === false) {
     1269                                        $parsedFrame[$frame_frequency]['adjustment'] *= -1;
     1270                                }
     1271                                $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
     1272                        }
     1273                        unset($parsedFrame['data']);
     1274
     1275
     1276                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
     1277                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
     1278                        //   There may only be one 'RVRB' frame in each tag.
     1279                        // <Header for 'Reverb', ID: 'RVRB'>
     1280                        // Reverb left (ms)                 $xx xx
     1281                        // Reverb right (ms)                $xx xx
     1282                        // Reverb bounces, left             $xx
     1283                        // Reverb bounces, right            $xx
     1284                        // Reverb feedback, left to left    $xx
     1285                        // Reverb feedback, left to right   $xx
     1286                        // Reverb feedback, right to right  $xx
     1287                        // Reverb feedback, right to left   $xx
     1288                        // Premix left to right             $xx
     1289                        // Premix right to left             $xx
     1290
     1291                        $frame_offset = 0;
     1292                        $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
     1293                        $frame_offset += 2;
     1294                        $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
     1295                        $frame_offset += 2;
     1296                        $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1297                        $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1298                        $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1299                        $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1300                        $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1301                        $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1302                        $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1303                        $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1304                        unset($parsedFrame['data']);
     1305
     1306
     1307                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
     1308                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
     1309                        //   There may be several pictures attached to one file,
     1310                        //   each in their individual 'APIC' frame, but only one
     1311                        //   with the same content descriptor
     1312                        // <Header for 'Attached picture', ID: 'APIC'>
     1313                        // Text encoding      $xx
     1314                        // ID3v2.3+ => MIME type          <text string> $00
     1315                        // ID3v2.2  => Image format       $xx xx xx
     1316                        // Picture type       $xx
     1317                        // Description        <text string according to encoding> $00 (00)
     1318                        // Picture data       <binary data>
     1319
     1320                        $frame_offset = 0;
     1321                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1322                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1323                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1324                        }
     1325
     1326                        if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
     1327                                $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
     1328                                if (strtolower($frame_imagetype) == 'ima') {
     1329                                        // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
     1330                                        // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
     1331                                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1332                                        $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1333                                        if (ord($frame_mimetype) === 0) {
     1334                                                $frame_mimetype = '';
     1335                                        }
     1336                                        $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
     1337                                        if ($frame_imagetype == 'JPEG') {
     1338                                                $frame_imagetype = 'JPG';
     1339                                        }
     1340                                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1341                                } else {
     1342                                        $frame_offset += 3;
     1343                                }
     1344                        }
     1345                        if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
     1346                                $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1347                                $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1348                                if (ord($frame_mimetype) === 0) {
     1349                                        $frame_mimetype = '';
     1350                                }
     1351                                $frame_offset = $frame_terminatorpos + strlen("\x00");
     1352                        }
     1353
     1354                        $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1355
     1356                        if ($frame_offset >= $parsedFrame['datalength']) {
     1357                                $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
     1358                        } else {
     1359                                $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1360                                if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1361                                        $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1362                                }
     1363                                $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1364                                if (ord($frame_description) === 0) {
     1365                                        $frame_description = '';
     1366                                }
     1367                                $parsedFrame['encodingid']       = $frame_textencoding;
     1368                                $parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
     1369
     1370                                if ($id3v2_majorversion == 2) {
     1371                                        $parsedFrame['imagetype']    = $frame_imagetype;
     1372                                } else {
     1373                                        $parsedFrame['mime']         = $frame_mimetype;
     1374                                }
     1375                                $parsedFrame['picturetypeid']    = $frame_picturetype;
     1376                                $parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
     1377                                $parsedFrame['description']      = $frame_description;
     1378                                $parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
     1379                                $parsedFrame['datalength']       = strlen($parsedFrame['data']);
     1380
     1381                                $parsedFrame['image_mime'] = '';
     1382                                $imageinfo = array();
     1383                                $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
     1384                                if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
     1385                                        $parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
     1386                                        if ($imagechunkcheck[0]) {
     1387                                                $parsedFrame['image_width']  = $imagechunkcheck[0];
     1388                                        }
     1389                                        if ($imagechunkcheck[1]) {
     1390                                                $parsedFrame['image_height'] = $imagechunkcheck[1];
     1391                                        }
     1392                                }
     1393
     1394                                do {
     1395                                        if ($this->getid3->option_save_attachments === false) {
     1396                                                // skip entirely
     1397                                                unset($parsedFrame['data']);
     1398                                                break;
     1399                                        }
     1400                                        if ($this->getid3->option_save_attachments === true) {
     1401                                                // great
     1402/*
     1403                                        } elseif (is_int($this->getid3->option_save_attachments)) {
     1404                                                if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
     1405                                                        // too big, skip
     1406                                                        $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
     1407                                                        unset($parsedFrame['data']);
     1408                                                        break;
     1409                                                }
     1410*/
     1411                                        } elseif (is_string($this->getid3->option_save_attachments)) {
     1412                                                $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
     1413                                                if (!is_dir($dir) || !is_writable($dir)) {
     1414                                                        // cannot write, skip
     1415                                                        $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
     1416                                                        unset($parsedFrame['data']);
     1417                                                        break;
     1418                                                }
     1419                                        }
     1420                                        // if we get this far, must be OK
     1421                                        if (is_string($this->getid3->option_save_attachments)) {
     1422                                                $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
     1423                                                if (!file_exists($destination_filename) || is_writable($destination_filename)) {
     1424                                                        file_put_contents($destination_filename, $parsedFrame['data']);
     1425                                                } else {
     1426                                                        $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
     1427                                                }
     1428                                                $parsedFrame['data_filename'] = $destination_filename;
     1429                                                unset($parsedFrame['data']);
     1430                                        } else {
     1431                                                if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     1432                                                        if (!isset($info['id3v2']['comments']['picture'])) {
     1433                                                                $info['id3v2']['comments']['picture'] = array();
     1434                                                        }
     1435                                                        $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
     1436                                                }
     1437                                        }
     1438                                } while (false);
     1439                        }
     1440
     1441                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
     1442                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
     1443                        //   There may be more than one 'GEOB' frame in each tag,
     1444                        //   but only one with the same content descriptor
     1445                        // <Header for 'General encapsulated object', ID: 'GEOB'>
     1446                        // Text encoding          $xx
     1447                        // MIME type              <text string> $00
     1448                        // Filename               <text string according to encoding> $00 (00)
     1449                        // Content description    <text string according to encoding> $00 (00)
     1450                        // Encapsulated object    <binary data>
     1451
     1452                        $frame_offset = 0;
     1453                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1454                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1455                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1456                        }
     1457                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1458                        $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1459                        if (ord($frame_mimetype) === 0) {
     1460                                $frame_mimetype = '';
     1461                        }
     1462                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1463
     1464                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1465                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1466                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1467                        }
     1468                        $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1469                        if (ord($frame_filename) === 0) {
     1470                                $frame_filename = '';
     1471                        }
     1472                        $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
     1473
     1474                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1475                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1476                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1477                        }
     1478                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1479                        if (ord($frame_description) === 0) {
     1480                                $frame_description = '';
     1481                        }
     1482                        $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
     1483
     1484                        $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
     1485                        $parsedFrame['encodingid']  = $frame_textencoding;
     1486                        $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
     1487
     1488                        $parsedFrame['mime']        = $frame_mimetype;
     1489                        $parsedFrame['filename']    = $frame_filename;
     1490                        $parsedFrame['description'] = $frame_description;
     1491                        unset($parsedFrame['data']);
     1492
     1493
     1494                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
     1495                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
     1496                        //   There may only be one 'PCNT' frame in each tag.
     1497                        //   When the counter reaches all one's, one byte is inserted in
     1498                        //   front of the counter thus making the counter eight bits bigger
     1499                        // <Header for 'Play counter', ID: 'PCNT'>
     1500                        // Counter        $xx xx xx xx (xx ...)
     1501
     1502                        $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
     1503
     1504
     1505                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
     1506                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
     1507                        //   There may be more than one 'POPM' frame in each tag,
     1508                        //   but only one with the same email address
     1509                        // <Header for 'Popularimeter', ID: 'POPM'>
     1510                        // Email to user   <text string> $00
     1511                        // Rating          $xx
     1512                        // Counter         $xx xx xx xx (xx ...)
     1513
     1514                        $frame_offset = 0;
     1515                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1516                        $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1517                        if (ord($frame_emailaddress) === 0) {
     1518                                $frame_emailaddress = '';
     1519                        }
     1520                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1521                        $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1522                        $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
     1523                        $parsedFrame['email']   = $frame_emailaddress;
     1524                        $parsedFrame['rating']  = $frame_rating;
     1525                        unset($parsedFrame['data']);
     1526
     1527
     1528                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
     1529                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
     1530                        //   There may only be one 'RBUF' frame in each tag
     1531                        // <Header for 'Recommended buffer size', ID: 'RBUF'>
     1532                        // Buffer size               $xx xx xx
     1533                        // Embedded info flag        %0000000x
     1534                        // Offset to next tag        $xx xx xx xx
     1535
     1536                        $frame_offset = 0;
     1537                        $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
     1538                        $frame_offset += 3;
     1539
     1540                        $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
     1541                        $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
     1542                        $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     1543                        unset($parsedFrame['data']);
     1544
     1545
     1546                } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
     1547                        //   There may be more than one 'CRM' frame in a tag,
     1548                        //   but only one with the same 'owner identifier'
     1549                        // <Header for 'Encrypted meta frame', ID: 'CRM'>
     1550                        // Owner identifier      <textstring> $00 (00)
     1551                        // Content/explanation   <textstring> $00 (00)
     1552                        // Encrypted datablock   <binary data>
     1553
     1554                        $frame_offset = 0;
     1555                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1556                        $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1557                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1558
     1559                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1560                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1561                        if (ord($frame_description) === 0) {
     1562                                $frame_description = '';
     1563                        }
     1564                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1565
     1566                        $parsedFrame['ownerid']     = $frame_ownerid;
     1567                        $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
     1568                        $parsedFrame['description'] = $frame_description;
     1569                        unset($parsedFrame['data']);
     1570
     1571
     1572                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
     1573                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
     1574                        //   There may be more than one 'AENC' frames in a tag,
     1575                        //   but only one with the same 'Owner identifier'
     1576                        // <Header for 'Audio encryption', ID: 'AENC'>
     1577                        // Owner identifier   <text string> $00
     1578                        // Preview start      $xx xx
     1579                        // Preview length     $xx xx
     1580                        // Encryption info    <binary data>
     1581
     1582                        $frame_offset = 0;
     1583                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1584                        $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1585                        if (ord($frame_ownerid) === 0) {
     1586                                $frame_ownerid == '';
     1587                        }
     1588                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1589                        $parsedFrame['ownerid'] = $frame_ownerid;
     1590                        $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
     1591                        $frame_offset += 2;
     1592                        $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
     1593                        $frame_offset += 2;
     1594                        $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
     1595                        unset($parsedFrame['data']);
     1596
     1597
     1598                } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
     1599                                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {     // 4.22  LNK  Linked information
     1600                        //   There may be more than one 'LINK' frame in a tag,
     1601                        //   but only one with the same contents
     1602                        // <Header for 'Linked information', ID: 'LINK'>
     1603                        // ID3v2.3+ => Frame identifier   $xx xx xx xx
     1604                        // ID3v2.2  => Frame identifier   $xx xx xx
     1605                        // URL                            <text string> $00
     1606                        // ID and additional data         <text string(s)>
     1607
     1608                        $frame_offset = 0;
     1609                        if ($id3v2_majorversion == 2) {
     1610                                $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
     1611                                $frame_offset += 3;
     1612                        } else {
     1613                                $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
     1614                                $frame_offset += 4;
     1615                        }
     1616
     1617                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1618                        $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1619                        if (ord($frame_url) === 0) {
     1620                                $frame_url = '';
     1621                        }
     1622                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1623                        $parsedFrame['url'] = $frame_url;
     1624
     1625                        $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
     1626                        if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
     1627                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
     1628                        }
     1629                        unset($parsedFrame['data']);
     1630
     1631
     1632                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
     1633                        //   There may only be one 'POSS' frame in each tag
     1634                        // <Head for 'Position synchronisation', ID: 'POSS'>
     1635                        // Time stamp format         $xx
     1636                        // Position                  $xx (xx ...)
     1637
     1638                        $frame_offset = 0;
     1639                        $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1640                        $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
     1641                        unset($parsedFrame['data']);
     1642
     1643
     1644                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
     1645                        //   There may be more than one 'Terms of use' frame in a tag,
     1646                        //   but only one with the same 'Language'
     1647                        // <Header for 'Terms of use frame', ID: 'USER'>
     1648                        // Text encoding        $xx
     1649                        // Language             $xx xx xx
     1650                        // The actual text      <text string according to encoding>
     1651
     1652                        $frame_offset = 0;
     1653                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1654                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1655                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1656                        }
     1657                        $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
     1658                        $frame_offset += 3;
     1659                        $parsedFrame['language']     = $frame_language;
     1660                        $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
     1661                        $parsedFrame['encodingid']   = $frame_textencoding;
     1662                        $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
     1663
     1664                        $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
     1665                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
     1666                                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
     1667                        }
     1668                        unset($parsedFrame['data']);
     1669
     1670
     1671                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
     1672                        //   There may only be one 'OWNE' frame in a tag
     1673                        // <Header for 'Ownership frame', ID: 'OWNE'>
     1674                        // Text encoding     $xx
     1675                        // Price paid        <text string> $00
     1676                        // Date of purch.    <text string>
     1677                        // Seller            <text string according to encoding>
     1678
     1679                        $frame_offset = 0;
     1680                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1681                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1682                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1683                        }
     1684                        $parsedFrame['encodingid'] = $frame_textencoding;
     1685                        $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
     1686
     1687                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1688                        $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1689                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1690
     1691                        $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
     1692                        $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
     1693                        $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
     1694
     1695                        $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
     1696                        if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
     1697                                $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
     1698                        }
     1699                        $frame_offset += 8;
     1700
     1701                        $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
     1702                        unset($parsedFrame['data']);
     1703
     1704
     1705                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
     1706                        //   There may be more than one 'commercial frame' in a tag,
     1707                        //   but no two may be identical
     1708                        // <Header for 'Commercial frame', ID: 'COMR'>
     1709                        // Text encoding      $xx
     1710                        // Price string       <text string> $00
     1711                        // Valid until        <text string>
     1712                        // Contact URL        <text string> $00
     1713                        // Received as        $xx
     1714                        // Name of seller     <text string according to encoding> $00 (00)
     1715                        // Description        <text string according to encoding> $00 (00)
     1716                        // Picture MIME type  <string> $00
     1717                        // Seller logo        <binary data>
     1718
     1719                        $frame_offset = 0;
     1720                        $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1721                        if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
     1722                                $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
     1723                        }
     1724
     1725                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1726                        $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1727                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1728                        $frame_rawpricearray = explode('/', $frame_pricestring);
     1729                        foreach ($frame_rawpricearray as $key => $val) {
     1730                                $frame_currencyid = substr($val, 0, 3);
     1731                                $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
     1732                                $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
     1733                        }
     1734
     1735                        $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
     1736                        $frame_offset += 8;
     1737
     1738                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1739                        $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1740                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1741
     1742                        $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1743
     1744                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1745                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1746                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1747                        }
     1748                        $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1749                        if (ord($frame_sellername) === 0) {
     1750                                $frame_sellername = '';
     1751                        }
     1752                        $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
     1753
     1754                        $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
     1755                        if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
     1756                                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
     1757                        }
     1758                        $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1759                        if (ord($frame_description) === 0) {
     1760                                $frame_description = '';
     1761                        }
     1762                        $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
     1763
     1764                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1765                        $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1766                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1767
     1768                        $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
     1769
     1770                        $parsedFrame['encodingid']        = $frame_textencoding;
     1771                        $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
     1772
     1773                        $parsedFrame['pricevaliduntil']   = $frame_datestring;
     1774                        $parsedFrame['contacturl']        = $frame_contacturl;
     1775                        $parsedFrame['receivedasid']      = $frame_receivedasid;
     1776                        $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
     1777                        $parsedFrame['sellername']        = $frame_sellername;
     1778                        $parsedFrame['description']       = $frame_description;
     1779                        $parsedFrame['mime']              = $frame_mimetype;
     1780                        $parsedFrame['logo']              = $frame_sellerlogo;
     1781                        unset($parsedFrame['data']);
     1782
     1783
     1784                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
     1785                        //   There may be several 'ENCR' frames in a tag,
     1786                        //   but only one containing the same symbol
     1787                        //   and only one containing the same owner identifier
     1788                        // <Header for 'Encryption method registration', ID: 'ENCR'>
     1789                        // Owner identifier    <text string> $00
     1790                        // Method symbol       $xx
     1791                        // Encryption data     <binary data>
     1792
     1793                        $frame_offset = 0;
     1794                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1795                        $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1796                        if (ord($frame_ownerid) === 0) {
     1797                                $frame_ownerid = '';
     1798                        }
     1799                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1800
     1801                        $parsedFrame['ownerid']      = $frame_ownerid;
     1802                        $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1803                        $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
     1804
     1805
     1806                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
     1807
     1808                        //   There may be several 'GRID' frames in a tag,
     1809                        //   but only one containing the same symbol
     1810                        //   and only one containing the same owner identifier
     1811                        // <Header for 'Group ID registration', ID: 'GRID'>
     1812                        // Owner identifier      <text string> $00
     1813                        // Group symbol          $xx
     1814                        // Group dependent data  <binary data>
     1815
     1816                        $frame_offset = 0;
     1817                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1818                        $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1819                        if (ord($frame_ownerid) === 0) {
     1820                                $frame_ownerid = '';
     1821                        }
     1822                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1823
     1824                        $parsedFrame['ownerid']       = $frame_ownerid;
     1825                        $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1826                        $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
     1827
     1828
     1829                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
     1830                        //   The tag may contain more than one 'PRIV' frame
     1831                        //   but only with different contents
     1832                        // <Header for 'Private frame', ID: 'PRIV'>
     1833                        // Owner identifier      <text string> $00
     1834                        // The private data      <binary data>
     1835
     1836                        $frame_offset = 0;
     1837                        $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
     1838                        $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
     1839                        if (ord($frame_ownerid) === 0) {
     1840                                $frame_ownerid = '';
     1841                        }
     1842                        $frame_offset = $frame_terminatorpos + strlen("\x00");
     1843
     1844                        $parsedFrame['ownerid'] = $frame_ownerid;
     1845                        $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
     1846
     1847
     1848                } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
     1849                        //   There may be more than one 'signature frame' in a tag,
     1850                        //   but no two may be identical
     1851                        // <Header for 'Signature frame', ID: 'SIGN'>
     1852                        // Group symbol      $xx
     1853                        // Signature         <binary data>
     1854
     1855                        $frame_offset = 0;
     1856                        $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1857                        $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
     1858
     1859
     1860                } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
     1861                        //   There may only be one 'seek frame' in a tag
     1862                        // <Header for 'Seek frame', ID: 'SEEK'>
     1863                        // Minimum offset to next tag       $xx xx xx xx
     1864
     1865                        $frame_offset = 0;
     1866                        $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     1867
     1868
     1869                } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
     1870                        //   There may only be one 'audio seek point index' frame in a tag
     1871                        // <Header for 'Seek Point Index', ID: 'ASPI'>
     1872                        // Indexed data start (S)         $xx xx xx xx
     1873                        // Indexed data length (L)        $xx xx xx xx
     1874                        // Number of index points (N)     $xx xx
     1875                        // Bits per index point (b)       $xx
     1876                        //   Then for every index point the following data is included:
     1877                        // Fraction at index (Fi)          $xx (xx)
     1878
     1879                        $frame_offset = 0;
     1880                        $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     1881                        $frame_offset += 4;
     1882                        $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
     1883                        $frame_offset += 4;
     1884                        $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
     1885                        $frame_offset += 2;
     1886                        $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
     1887                        $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
     1888                        for ($i = 0; $i < $frame_indexpoints; $i++) {
     1889                                $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
     1890                                $frame_offset += $frame_bytesperpoint;
     1891                        }
     1892                        unset($parsedFrame['data']);
     1893
     1894                } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
     1895                        // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
     1896                        //   There may only be one 'RGAD' frame in a tag
     1897                        // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
     1898                        // Peak Amplitude                      $xx $xx $xx $xx
     1899                        // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
     1900                        // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
     1901                        //   a - name code
     1902                        //   b - originator code
     1903                        //   c - sign bit
     1904                        //   d - replay gain adjustment
     1905
     1906                        $frame_offset = 0;
     1907                        $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
     1908                        $frame_offset += 4;
     1909                        $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
     1910                        $frame_offset += 2;
     1911                        $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
     1912                        $frame_offset += 2;
     1913                        $parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
     1914                        $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
     1915                        $parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
     1916                        $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
     1917                        $parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
     1918                        $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
     1919                        $parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
     1920                        $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
     1921                        $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
     1922                        $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
     1923                        $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
     1924                        $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
     1925                        $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
     1926                        $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
     1927
     1928                        $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
     1929                        $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
     1930                        $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
     1931                        $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
     1932                        $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
     1933
     1934                        unset($parsedFrame['data']);
     1935
     1936                }
     1937
     1938                return true;
     1939        }
     1940
     1941
     1942        public function DeUnsynchronise($data) {
     1943                return str_replace("\xFF\x00", "\xFF", $data);
     1944        }
     1945
     1946        public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
     1947                static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
     1948                        0x00 => 'No more than 128 frames and 1 MB total tag size',
     1949                        0x01 => 'No more than 64 frames and 128 KB total tag size',
     1950                        0x02 => 'No more than 32 frames and 40 KB total tag size',
     1951                        0x03 => 'No more than 32 frames and 4 KB total tag size',
     1952                );
     1953                return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
     1954        }
     1955
     1956        public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
     1957                static $LookupExtendedHeaderRestrictionsTextEncodings = array(
     1958                        0x00 => 'No restrictions',
     1959                        0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
     1960                );
     1961                return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
     1962        }
     1963
     1964        public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
     1965                static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
     1966                        0x00 => 'No restrictions',
     1967                        0x01 => 'No string is longer than 1024 characters',
     1968                        0x02 => 'No string is longer than 128 characters',
     1969                        0x03 => 'No string is longer than 30 characters',
     1970                );
     1971                return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
     1972        }
     1973
     1974        public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
     1975                static $LookupExtendedHeaderRestrictionsImageEncoding = array(
     1976                        0x00 => 'No restrictions',
     1977                        0x01 => 'Images are encoded only with PNG or JPEG',
     1978                );
     1979                return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
     1980        }
     1981
     1982        public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
     1983                static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
     1984                        0x00 => 'No restrictions',
     1985                        0x01 => 'All images are 256x256 pixels or smaller',
     1986                        0x02 => 'All images are 64x64 pixels or smaller',
     1987                        0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
     1988                );
     1989                return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
     1990        }
     1991
     1992        public function LookupCurrencyUnits($currencyid) {
     1993
     1994                $begin = __LINE__;
     1995
     1996                /** This is not a comment!
     1997
     1998
     1999                        AED     Dirhams
     2000                        AFA     Afghanis
     2001                        ALL     Leke
     2002                        AMD     Drams
     2003                        ANG     Guilders
     2004                        AOA     Kwanza
     2005                        ARS     Pesos
     2006                        ATS     Schillings
     2007                        AUD     Dollars
     2008                        AWG     Guilders
     2009                        AZM     Manats
     2010                        BAM     Convertible Marka
     2011                        BBD     Dollars
     2012                        BDT     Taka
     2013                        BEF     Francs
     2014                        BGL     Leva
     2015                        BHD     Dinars
     2016                        BIF     Francs
     2017                        BMD     Dollars
     2018                        BND     Dollars
     2019                        BOB     Bolivianos
     2020                        BRL     Brazil Real
     2021                        BSD     Dollars
     2022                        BTN     Ngultrum
     2023                        BWP     Pulas
     2024                        BYR     Rubles
     2025                        BZD     Dollars
     2026                        CAD     Dollars
     2027                        CDF     Congolese Francs
     2028                        CHF     Francs
     2029                        CLP     Pesos
     2030                        CNY     Yuan Renminbi
     2031                        COP     Pesos
     2032                        CRC     Colones
     2033                        CUP     Pesos
     2034                        CVE     Escudos
     2035                        CYP     Pounds
     2036                        CZK     Koruny
     2037                        DEM     Deutsche Marks
     2038                        DJF     Francs
     2039                        DKK     Kroner
     2040                        DOP     Pesos
     2041                        DZD     Algeria Dinars
     2042                        EEK     Krooni
     2043                        EGP     Pounds
     2044                        ERN     Nakfa
     2045                        ESP     Pesetas
     2046                        ETB     Birr
     2047                        EUR     Euro
     2048                        FIM     Markkaa
     2049                        FJD     Dollars
     2050                        FKP     Pounds
     2051                        FRF     Francs
     2052                        GBP     Pounds
     2053                        GEL     Lari
     2054                        GGP     Pounds
     2055                        GHC     Cedis
     2056                        GIP     Pounds
     2057                        GMD     Dalasi
     2058                        GNF     Francs
     2059                        GRD     Drachmae
     2060                        GTQ     Quetzales
     2061                        GYD     Dollars
     2062                        HKD     Dollars
     2063                        HNL     Lempiras
     2064                        HRK     Kuna
     2065                        HTG     Gourdes
     2066                        HUF     Forints
     2067                        IDR     Rupiahs
     2068                        IEP     Pounds
     2069                        ILS     New Shekels
     2070                        IMP     Pounds
     2071                        INR     Rupees
     2072                        IQD     Dinars
     2073                        IRR     Rials
     2074                        ISK     Kronur
     2075                        ITL     Lire
     2076                        JEP     Pounds
     2077                        JMD     Dollars
     2078                        JOD     Dinars
     2079                        JPY     Yen
     2080                        KES     Shillings
     2081                        KGS     Soms
     2082                        KHR     Riels
     2083                        KMF     Francs
     2084                        KPW     Won
     2085                        KWD     Dinars
     2086                        KYD     Dollars
     2087                        KZT     Tenge
     2088                        LAK     Kips
     2089                        LBP     Pounds
     2090                        LKR     Rupees
     2091                        LRD     Dollars
     2092                        LSL     Maloti
     2093                        LTL     Litai
     2094                        LUF     Francs
     2095                        LVL     Lati
     2096                        LYD     Dinars
     2097                        MAD     Dirhams
     2098                        MDL     Lei
     2099                        MGF     Malagasy Francs
     2100                        MKD     Denars
     2101                        MMK     Kyats
     2102                        MNT     Tugriks
     2103                        MOP     Patacas
     2104                        MRO     Ouguiyas
     2105                        MTL     Liri
     2106                        MUR     Rupees
     2107                        MVR     Rufiyaa
     2108                        MWK     Kwachas
     2109                        MXN     Pesos
     2110                        MYR     Ringgits
     2111                        MZM     Meticais
     2112                        NAD     Dollars
     2113                        NGN     Nairas
     2114                        NIO     Gold Cordobas
     2115                        NLG     Guilders
     2116                        NOK     Krone
     2117                        NPR     Nepal Rupees
     2118                        NZD     Dollars
     2119                        OMR     Rials
     2120                        PAB     Balboa
     2121                        PEN     Nuevos Soles
     2122                        PGK     Kina
     2123                        PHP     Pesos
     2124                        PKR     Rupees
     2125                        PLN     Zlotych
     2126                        PTE     Escudos
     2127                        PYG     Guarani
     2128                        QAR     Rials
     2129                        ROL     Lei
     2130                        RUR     Rubles
     2131                        RWF     Rwanda Francs
     2132                        SAR     Riyals
     2133                        SBD     Dollars
     2134                        SCR     Rupees
     2135                        SDD     Dinars
     2136                        SEK     Kronor
     2137                        SGD     Dollars
     2138                        SHP     Pounds
     2139                        SIT     Tolars
     2140                        SKK     Koruny
     2141                        SLL     Leones
     2142                        SOS     Shillings
     2143                        SPL     Luigini
     2144                        SRG     Guilders
     2145                        STD     Dobras
     2146                        SVC     Colones
     2147                        SYP     Pounds
     2148                        SZL     Emalangeni
     2149                        THB     Baht
     2150                        TJR     Rubles
     2151                        TMM     Manats
     2152                        TND     Dinars
     2153                        TOP     Pa'anga
     2154                        TRL     Liras
     2155                        TTD     Dollars
     2156                        TVD     Tuvalu Dollars
     2157                        TWD     New Dollars
     2158                        TZS     Shillings
     2159                        UAH     Hryvnia
     2160                        UGX     Shillings
     2161                        USD     Dollars
     2162                        UYU     Pesos
     2163                        UZS     Sums
     2164                        VAL     Lire
     2165                        VEB     Bolivares
     2166                        VND     Dong
     2167                        VUV     Vatu
     2168                        WST     Tala
     2169                        XAF     Francs
     2170                        XAG     Ounces
     2171                        XAU     Ounces
     2172                        XCD     Dollars
     2173                        XDR     Special Drawing Rights
     2174                        XPD     Ounces
     2175                        XPF     Francs
     2176                        XPT     Ounces
     2177                        YER     Rials
     2178                        YUM     New Dinars
     2179                        ZAR     Rand
     2180                        ZMK     Kwacha
     2181                        ZWD     Zimbabwe Dollars
     2182
     2183                */
     2184
     2185                return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
     2186        }
     2187
     2188
     2189        public function LookupCurrencyCountry($currencyid) {
     2190
     2191                $begin = __LINE__;
     2192
     2193                /** This is not a comment!
     2194
     2195                        AED     United Arab Emirates
     2196                        AFA     Afghanistan
     2197                        ALL     Albania
     2198                        AMD     Armenia
     2199                        ANG     Netherlands Antilles
     2200                        AOA     Angola
     2201                        ARS     Argentina
     2202                        ATS     Austria
     2203                        AUD     Australia
     2204                        AWG     Aruba
     2205                        AZM     Azerbaijan
     2206                        BAM     Bosnia and Herzegovina
     2207                        BBD     Barbados
     2208                        BDT     Bangladesh
     2209                        BEF     Belgium
     2210                        BGL     Bulgaria
     2211                        BHD     Bahrain
     2212                        BIF     Burundi
     2213                        BMD     Bermuda
     2214                        BND     Brunei Darussalam
     2215                        BOB     Bolivia
     2216                        BRL     Brazil
     2217                        BSD     Bahamas
     2218                        BTN     Bhutan
     2219                        BWP     Botswana
     2220                        BYR     Belarus
     2221                        BZD     Belize
     2222                        CAD     Canada
     2223                        CDF     Congo/Kinshasa
     2224                        CHF     Switzerland
     2225                        CLP     Chile
     2226                        CNY     China
     2227                        COP     Colombia
     2228                        CRC     Costa Rica
     2229                        CUP     Cuba
     2230                        CVE     Cape Verde
     2231                        CYP     Cyprus
     2232                        CZK     Czech Republic
     2233                        DEM     Germany
     2234                        DJF     Djibouti
     2235                        DKK     Denmark
     2236                        DOP     Dominican Republic
     2237                        DZD     Algeria
     2238                        EEK     Estonia
     2239                        EGP     Egypt
     2240                        ERN     Eritrea
     2241                        ESP     Spain
     2242                        ETB     Ethiopia
     2243                        EUR     Euro Member Countries
     2244                        FIM     Finland
     2245                        FJD     Fiji
     2246                        FKP     Falkland Islands (Malvinas)
     2247                        FRF     France
     2248                        GBP     United Kingdom
     2249                        GEL     Georgia
     2250                        GGP     Guernsey
     2251                        GHC     Ghana
     2252                        GIP     Gibraltar
     2253                        GMD     Gambia
     2254                        GNF     Guinea
     2255                        GRD     Greece
     2256                        GTQ     Guatemala
     2257                        GYD     Guyana
     2258                        HKD     Hong Kong
     2259                        HNL     Honduras
     2260                        HRK     Croatia
     2261                        HTG     Haiti
     2262                        HUF     Hungary
     2263                        IDR     Indonesia
     2264                        IEP     Ireland (Eire)
     2265                        ILS     Israel
     2266                        IMP     Isle of Man
     2267                        INR     India
     2268                        IQD     Iraq
     2269                        IRR     Iran
     2270                        ISK     Iceland
     2271                        ITL     Italy
     2272                        JEP     Jersey
     2273                        JMD     Jamaica
     2274                        JOD     Jordan
     2275                        JPY     Japan
     2276                        KES     Kenya
     2277                        KGS     Kyrgyzstan
     2278                        KHR     Cambodia
     2279                        KMF     Comoros
     2280                        KPW     Korea
     2281                        KWD     Kuwait
     2282                        KYD     Cayman Islands
     2283                        KZT     Kazakstan
     2284                        LAK     Laos
     2285                        LBP     Lebanon
     2286                        LKR     Sri Lanka
     2287                        LRD     Liberia
     2288                        LSL     Lesotho
     2289                        LTL     Lithuania
     2290                        LUF     Luxembourg
     2291                        LVL     Latvia
     2292                        LYD     Libya
     2293                        MAD     Morocco
     2294                        MDL     Moldova
     2295                        MGF     Madagascar
     2296                        MKD     Macedonia
     2297                        MMK     Myanmar (Burma)
     2298                        MNT     Mongolia
     2299                        MOP     Macau
     2300                        MRO     Mauritania
     2301                        MTL     Malta
     2302                        MUR     Mauritius
     2303                        MVR     Maldives (Maldive Islands)
     2304                        MWK     Malawi
     2305                        MXN     Mexico
     2306                        MYR     Malaysia
     2307                        MZM     Mozambique
     2308                        NAD     Namibia
     2309                        NGN     Nigeria
     2310                        NIO     Nicaragua
     2311                        NLG     Netherlands (Holland)
     2312                        NOK     Norway
     2313                        NPR     Nepal
     2314                        NZD     New Zealand
     2315                        OMR     Oman
     2316                        PAB     Panama
     2317                        PEN     Peru
     2318                        PGK     Papua New Guinea
     2319                        PHP     Philippines
     2320                        PKR     Pakistan
     2321                        PLN     Poland
     2322                        PTE     Portugal
     2323                        PYG     Paraguay
     2324                        QAR     Qatar
     2325                        ROL     Romania
     2326                        RUR     Russia
     2327                        RWF     Rwanda
     2328                        SAR     Saudi Arabia
     2329                        SBD     Solomon Islands
     2330                        SCR     Seychelles
     2331                        SDD     Sudan
     2332                        SEK     Sweden
     2333                        SGD     Singapore
     2334                        SHP     Saint Helena
     2335                        SIT     Slovenia
     2336                        SKK     Slovakia
     2337                        SLL     Sierra Leone
     2338                        SOS     Somalia
     2339                        SPL     Seborga
     2340                        SRG     Suriname
     2341                        STD     São Tome and Principe
     2342                        SVC     El Salvador
     2343                        SYP     Syria
     2344                        SZL     Swaziland
     2345                        THB     Thailand
     2346                        TJR     Tajikistan
     2347                        TMM     Turkmenistan
     2348                        TND     Tunisia
     2349                        TOP     Tonga
     2350                        TRL     Turkey
     2351                        TTD     Trinidad and Tobago
     2352                        TVD     Tuvalu
     2353                        TWD     Taiwan
     2354                        TZS     Tanzania
     2355                        UAH     Ukraine
     2356                        UGX     Uganda
     2357                        USD     United States of America
     2358                        UYU     Uruguay
     2359                        UZS     Uzbekistan
     2360                        VAL     Vatican City
     2361                        VEB     Venezuela
     2362                        VND     Viet Nam
     2363                        VUV     Vanuatu
     2364                        WST     Samoa
     2365                        XAF     Communauté Financière Africaine
     2366                        XAG     Silver
     2367                        XAU     Gold
     2368                        XCD     East Caribbean
     2369                        XDR     International Monetary Fund
     2370                        XPD     Palladium
     2371                        XPF     Comptoirs Français du Pacifique
     2372                        XPT     Platinum
     2373                        YER     Yemen
     2374                        YUM     Yugoslavia
     2375                        ZAR     South Africa
     2376                        ZMK     Zambia
     2377                        ZWD     Zimbabwe
     2378
     2379                */
     2380
     2381                return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
     2382        }
     2383
     2384
     2385
     2386        public static function LanguageLookup($languagecode, $casesensitive=false) {
     2387
     2388                if (!$casesensitive) {
     2389                        $languagecode = strtolower($languagecode);
     2390                }
     2391
     2392                // http://www.id3.org/id3v2.4.0-structure.txt
     2393                // [4.   ID3v2 frame overview]
     2394                // The three byte language field, present in several frames, is used to
     2395                // describe the language of the frame's content, according to ISO-639-2
     2396                // [ISO-639-2]. The language should be represented in lower case. If the
     2397                // language is not known the string "XXX" should be used.
     2398
     2399
     2400                // ISO 639-2 - http://www.id3.org/iso639-2.html
     2401
     2402                $begin = __LINE__;
     2403
     2404                /** This is not a comment!
     2405
     2406                        XXX     unknown
     2407                        xxx     unknown
     2408                        aar     Afar
     2409                        abk     Abkhazian
     2410                        ace     Achinese
     2411                        ach     Acoli
     2412                        ada     Adangme
     2413                        afa     Afro-Asiatic (Other)
     2414                        afh     Afrihili
     2415                        afr     Afrikaans
     2416                        aka     Akan
     2417                        akk     Akkadian
     2418                        alb     Albanian
     2419                        ale     Aleut
     2420                        alg     Algonquian Languages
     2421                        amh     Amharic
     2422                        ang     English, Old (ca. 450-1100)
     2423                        apa     Apache Languages
     2424                        ara     Arabic
     2425                        arc     Aramaic
     2426                        arm     Armenian
     2427                        arn     Araucanian
     2428                        arp     Arapaho
     2429                        art     Artificial (Other)
     2430                        arw     Arawak
     2431                        asm     Assamese
     2432                        ath     Athapascan Languages
     2433                        ava     Avaric
     2434                        ave     Avestan
     2435                        awa     Awadhi
     2436                        aym     Aymara
     2437                        aze     Azerbaijani
     2438                        bad     Banda
     2439                        bai     Bamileke Languages
     2440                        bak     Bashkir
     2441                        bal     Baluchi
     2442                        bam     Bambara
     2443                        ban     Balinese
     2444                        baq     Basque
     2445                        bas     Basa
     2446                        bat     Baltic (Other)
     2447                        bej     Beja
     2448                        bel     Byelorussian
     2449                        bem     Bemba
     2450                        ben     Bengali
     2451                        ber     Berber (Other)
     2452                        bho     Bhojpuri
     2453                        bih     Bihari
     2454                        bik     Bikol
     2455                        bin     Bini
     2456                        bis     Bislama
     2457                        bla     Siksika
     2458                        bnt     Bantu (Other)
     2459                        bod     Tibetan
     2460                        bra     Braj
     2461                        bre     Breton
     2462                        bua     Buriat
     2463                        bug     Buginese
     2464                        bul     Bulgarian
     2465                        bur     Burmese
     2466                        cad     Caddo
     2467                        cai     Central American Indian (Other)
     2468                        car     Carib
     2469                        cat     Catalan
     2470                        cau     Caucasian (Other)
     2471                        ceb     Cebuano
     2472                        cel     Celtic (Other)
     2473                        ces     Czech
     2474                        cha     Chamorro
     2475                        chb     Chibcha
     2476                        che     Chechen
     2477                        chg     Chagatai
     2478                        chi     Chinese
     2479                        chm     Mari
     2480                        chn     Chinook jargon
     2481                        cho     Choctaw
     2482                        chr     Cherokee
     2483                        chu     Church Slavic
     2484                        chv     Chuvash
     2485                        chy     Cheyenne
     2486                        cop     Coptic
     2487                        cor     Cornish
     2488                        cos     Corsican
     2489                        cpe     Creoles and Pidgins, English-based (Other)
     2490                        cpf     Creoles and Pidgins, French-based (Other)
     2491                        cpp     Creoles and Pidgins, Portuguese-based (Other)
     2492                        cre     Cree
     2493                        crp     Creoles and Pidgins (Other)
     2494                        cus     Cushitic (Other)
     2495                        cym     Welsh
     2496                        cze     Czech
     2497                        dak     Dakota
     2498                        dan     Danish
     2499                        del     Delaware
     2500                        deu     German
     2501                        din     Dinka
     2502                        div     Divehi
     2503                        doi     Dogri
     2504                        dra     Dravidian (Other)
     2505                        dua     Duala
     2506                        dum     Dutch, Middle (ca. 1050-1350)
     2507                        dut     Dutch
     2508                        dyu     Dyula
     2509                        dzo     Dzongkha
     2510                        efi     Efik
     2511                        egy     Egyptian (Ancient)
     2512                        eka     Ekajuk
     2513                        ell     Greek, Modern (1453-)
     2514                        elx     Elamite
     2515                        eng     English
     2516                        enm     English, Middle (ca. 1100-1500)
     2517                        epo     Esperanto
     2518                        esk     Eskimo (Other)
     2519                        esl     Spanish
     2520                        est     Estonian
     2521                        eus     Basque
     2522                        ewe     Ewe
     2523                        ewo     Ewondo
     2524                        fan     Fang
     2525                        fao     Faroese
     2526                        fas     Persian
     2527                        fat     Fanti
     2528                        fij     Fijian
     2529                        fin     Finnish
     2530                        fiu     Finno-Ugrian (Other)
     2531                        fon     Fon
     2532                        fra     French
     2533                        fre     French
     2534                        frm     French, Middle (ca. 1400-1600)
     2535                        fro     French, Old (842- ca. 1400)
     2536                        fry     Frisian
     2537                        ful     Fulah
     2538                        gaa     Ga
     2539                        gae     Gaelic (Scots)
     2540                        gai     Irish
     2541                        gay     Gayo
     2542                        gdh     Gaelic (Scots)
     2543                        gem     Germanic (Other)
     2544                        geo     Georgian
     2545                        ger     German
     2546                        gez     Geez
     2547                        gil     Gilbertese
     2548                        glg     Gallegan
     2549                        gmh     German, Middle High (ca. 1050-1500)
     2550                        goh     German, Old High (ca. 750-1050)
     2551                        gon     Gondi
     2552                        got     Gothic
     2553                        grb     Grebo
     2554                        grc     Greek, Ancient (to 1453)
     2555                        gre     Greek, Modern (1453-)
     2556                        grn     Guarani
     2557                        guj     Gujarati
     2558                        hai     Haida
     2559                        hau     Hausa
     2560                        haw     Hawaiian
     2561                        heb     Hebrew
     2562                        her     Herero
     2563                        hil     Hiligaynon
     2564                        him     Himachali
     2565                        hin     Hindi
     2566                        hmo     Hiri Motu
     2567                        hun     Hungarian
     2568                        hup     Hupa
     2569                        hye     Armenian
     2570                        iba     Iban
     2571                        ibo     Igbo
     2572                        ice     Icelandic
     2573                        ijo     Ijo
     2574                        iku     Inuktitut
     2575                        ilo     Iloko
     2576                        ina     Interlingua (International Auxiliary language Association)
     2577                        inc     Indic (Other)
     2578                        ind     Indonesian
     2579                        ine     Indo-European (Other)
     2580                        ine     Interlingue
     2581                        ipk     Inupiak
     2582                        ira     Iranian (Other)
     2583                        iri     Irish
     2584                        iro     Iroquoian uages
     2585                        isl     Icelandic
     2586                        ita     Italian
     2587                        jav     Javanese
     2588                        jaw     Javanese
     2589                        jpn     Japanese
     2590                        jpr     Judeo-Persian
     2591                        jrb     Judeo-Arabic
     2592                        kaa     Kara-Kalpak
     2593                        kab     Kabyle
     2594                        kac     Kachin
     2595                        kal     Greenlandic
     2596                        kam     Kamba
     2597                        kan     Kannada
     2598                        kar     Karen
     2599                        kas     Kashmiri
     2600                        kat     Georgian
     2601                        kau     Kanuri
     2602                        kaw     Kawi
     2603                        kaz     Kazakh
     2604                        kha     Khasi
     2605                        khi     Khoisan (Other)
     2606                        khm     Khmer
     2607                        kho     Khotanese
     2608                        kik     Kikuyu
     2609                        kin     Kinyarwanda
     2610                        kir     Kirghiz
     2611                        kok     Konkani
     2612                        kom     Komi
     2613                        kon     Kongo
     2614                        kor     Korean
     2615                        kpe     Kpelle
     2616                        kro     Kru
     2617                        kru     Kurukh
     2618                        kua     Kuanyama
     2619                        kum     Kumyk
     2620                        kur     Kurdish
     2621                        kus     Kusaie
     2622                        kut     Kutenai
     2623                        lad     Ladino
     2624                        lah     Lahnda
     2625                        lam     Lamba
     2626                        lao     Lao
     2627                        lat     Latin
     2628                        lav     Latvian
     2629                        lez     Lezghian
     2630                        lin     Lingala
     2631                        lit     Lithuanian
     2632                        lol     Mongo
     2633                        loz     Lozi
     2634                        ltz     Letzeburgesch
     2635                        lub     Luba-Katanga
     2636                        lug     Ganda
     2637                        lui     Luiseno
     2638                        lun     Lunda
     2639                        luo     Luo (Kenya and Tanzania)
     2640                        mac     Macedonian
     2641                        mad     Madurese
     2642                        mag     Magahi
     2643                        mah     Marshall
     2644                        mai     Maithili
     2645                        mak     Macedonian
     2646                        mak     Makasar
     2647                        mal     Malayalam
     2648                        man     Mandingo
     2649                        mao     Maori
     2650                        map     Austronesian (Other)
     2651                        mar     Marathi
     2652                        mas     Masai
     2653                        max     Manx
     2654                        may     Malay
     2655                        men     Mende
     2656                        mga     Irish, Middle (900 - 1200)
     2657                        mic     Micmac
     2658                        min     Minangkabau
     2659                        mis     Miscellaneous (Other)
     2660                        mkh     Mon-Kmer (Other)
     2661                        mlg     Malagasy
     2662                        mlt     Maltese
     2663                        mni     Manipuri
     2664                        mno     Manobo Languages
     2665                        moh     Mohawk
     2666                        mol     Moldavian
     2667                        mon     Mongolian
     2668                        mos     Mossi
     2669                        mri     Maori
     2670                        msa     Malay
     2671                        mul     Multiple Languages
     2672                        mun     Munda Languages
     2673                        mus     Creek
     2674                        mwr     Marwari
     2675                        mya     Burmese
     2676                        myn     Mayan Languages
     2677                        nah     Aztec
     2678                        nai     North American Indian (Other)
     2679                        nau     Nauru
     2680                        nav     Navajo
     2681                        nbl     Ndebele, South
     2682                        nde     Ndebele, North
     2683                        ndo     Ndongo
     2684                        nep     Nepali
     2685                        new     Newari
     2686                        nic     Niger-Kordofanian (Other)
     2687                        niu     Niuean
     2688                        nla     Dutch
     2689                        nno     Norwegian (Nynorsk)
     2690                        non     Norse, Old
     2691                        nor     Norwegian
     2692                        nso     Sotho, Northern
     2693                        nub     Nubian Languages
     2694                        nya     Nyanja
     2695                        nym     Nyamwezi
     2696                        nyn     Nyankole
     2697                        nyo     Nyoro
     2698                        nzi     Nzima
     2699                        oci     Langue d'Oc (post 1500)
     2700                        oji     Ojibwa
     2701                        ori     Oriya
     2702                        orm     Oromo
     2703                        osa     Osage
     2704                        oss     Ossetic
     2705                        ota     Turkish, Ottoman (1500 - 1928)
     2706                        oto     Otomian Languages
     2707                        paa     Papuan-Australian (Other)
     2708                        pag     Pangasinan
     2709                        pal     Pahlavi
     2710                        pam     Pampanga
     2711                        pan     Panjabi
     2712                        pap     Papiamento
     2713                        pau     Palauan
     2714                        peo     Persian, Old (ca 600 - 400 B.C.)
     2715                        per     Persian
     2716                        phn     Phoenician
     2717                        pli     Pali
     2718                        pol     Polish
     2719                        pon     Ponape
     2720                        por     Portuguese
     2721                        pra     Prakrit uages
     2722                        pro     Provencal, Old (to 1500)
     2723                        pus     Pushto
     2724                        que     Quechua
     2725                        raj     Rajasthani
     2726                        rar     Rarotongan
     2727                        roa     Romance (Other)
     2728                        roh     Rhaeto-Romance
     2729                        rom     Romany
     2730                        ron     Romanian
     2731                        rum     Romanian
     2732                        run     Rundi
     2733                        rus     Russian
     2734                        sad     Sandawe
     2735                        sag     Sango
     2736                        sah     Yakut
     2737                        sai     South American Indian (Other)
     2738                        sal     Salishan Languages
     2739                        sam     Samaritan Aramaic
     2740                        san     Sanskrit
     2741                        sco     Scots
     2742                        scr     Serbo-Croatian
     2743                        sel     Selkup
     2744                        sem     Semitic (Other)
     2745                        sga     Irish, Old (to 900)
     2746                        shn     Shan
     2747                        sid     Sidamo
     2748                        sin     Singhalese
     2749                        sio     Siouan Languages
     2750                        sit     Sino-Tibetan (Other)
     2751                        sla     Slavic (Other)
     2752                        slk     Slovak
     2753                        slo     Slovak
     2754                        slv     Slovenian
     2755                        smi     Sami Languages
     2756                        smo     Samoan
     2757                        sna     Shona
     2758                        snd     Sindhi
     2759                        sog     Sogdian
     2760                        som     Somali
     2761                        son     Songhai
     2762                        sot     Sotho, Southern
     2763                        spa     Spanish
     2764                        sqi     Albanian
     2765                        srd     Sardinian
     2766                        srr     Serer
     2767                        ssa     Nilo-Saharan (Other)
     2768                        ssw     Siswant
     2769                        ssw     Swazi
     2770                        suk     Sukuma
     2771                        sun     Sudanese
     2772                        sus     Susu
     2773                        sux     Sumerian
     2774                        sve     Swedish
     2775                        swa     Swahili
     2776                        swe     Swedish
     2777                        syr     Syriac
     2778                        tah     Tahitian
     2779                        tam     Tamil
     2780                        tat     Tatar
     2781                        tel     Telugu
     2782                        tem     Timne
     2783                        ter     Tereno
     2784                        tgk     Tajik
     2785                        tgl     Tagalog
     2786                        tha     Thai
     2787                        tib     Tibetan
     2788                        tig     Tigre
     2789                        tir     Tigrinya
     2790                        tiv     Tivi
     2791                        tli     Tlingit
     2792                        tmh     Tamashek
     2793                        tog     Tonga (Nyasa)
     2794                        ton     Tonga (Tonga Islands)
     2795                        tru     Truk
     2796                        tsi     Tsimshian
     2797                        tsn     Tswana
     2798                        tso     Tsonga
     2799                        tuk     Turkmen
     2800                        tum     Tumbuka
     2801                        tur     Turkish
     2802                        tut     Altaic (Other)
     2803                        twi     Twi
     2804                        tyv     Tuvinian
     2805                        uga     Ugaritic
     2806                        uig     Uighur
     2807                        ukr     Ukrainian
     2808                        umb     Umbundu
     2809                        und     Undetermined
     2810                        urd     Urdu
     2811                        uzb     Uzbek
     2812                        vai     Vai
     2813                        ven     Venda
     2814                        vie     Vietnamese
     2815                        vol     Volapük
     2816                        vot     Votic
     2817                        wak     Wakashan Languages
     2818                        wal     Walamo
     2819                        war     Waray
     2820                        was     Washo
     2821                        wel     Welsh
     2822                        wen     Sorbian Languages
     2823                        wol     Wolof
     2824                        xho     Xhosa
     2825                        yao     Yao
     2826                        yap     Yap
     2827                        yid     Yiddish
     2828                        yor     Yoruba
     2829                        zap     Zapotec
     2830                        zen     Zenaga
     2831                        zha     Zhuang
     2832                        zho     Chinese
     2833                        zul     Zulu
     2834                        zun     Zuni
     2835
     2836                */
     2837
     2838                return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
     2839        }
     2840
     2841
     2842        public static function ETCOEventLookup($index) {
     2843                if (($index >= 0x17) && ($index <= 0xDF)) {
     2844                        return 'reserved for future use';
     2845                }
     2846                if (($index >= 0xE0) && ($index <= 0xEF)) {
     2847                        return 'not predefined synch 0-F';
     2848                }
     2849                if (($index >= 0xF0) && ($index <= 0xFC)) {
     2850                        return 'reserved for future use';
     2851                }
     2852
     2853                static $EventLookup = array(
     2854                        0x00 => 'padding (has no meaning)',
     2855                        0x01 => 'end of initial silence',
     2856                        0x02 => 'intro start',
     2857                        0x03 => 'main part start',
     2858                        0x04 => 'outro start',
     2859                        0x05 => 'outro end',
     2860                        0x06 => 'verse start',
     2861                        0x07 => 'refrain start',
     2862                        0x08 => 'interlude start',
     2863                        0x09 => 'theme start',
     2864                        0x0A => 'variation start',
     2865                        0x0B => 'key change',
     2866                        0x0C => 'time change',
     2867                        0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
     2868                        0x0E => 'sustained noise',
     2869                        0x0F => 'sustained noise end',
     2870                        0x10 => 'intro end',
     2871                        0x11 => 'main part end',
     2872                        0x12 => 'verse end',
     2873                        0x13 => 'refrain end',
     2874                        0x14 => 'theme end',
     2875                        0x15 => 'profanity',
     2876                        0x16 => 'profanity end',
     2877                        0xFD => 'audio end (start of silence)',
     2878                        0xFE => 'audio file ends',
     2879                        0xFF => 'one more byte of events follows'
     2880                );
     2881
     2882                return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
     2883        }
     2884
     2885        public static function SYTLContentTypeLookup($index) {
     2886                static $SYTLContentTypeLookup = array(
     2887                        0x00 => 'other',
     2888                        0x01 => 'lyrics',
     2889                        0x02 => 'text transcription',
     2890                        0x03 => 'movement/part name', // (e.g. 'Adagio')
     2891                        0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
     2892                        0x05 => 'chord',              // (e.g. 'Bb F Fsus')
     2893                        0x06 => 'trivia/\'pop up\' information',
     2894                        0x07 => 'URLs to webpages',
     2895                        0x08 => 'URLs to images'
     2896                );
     2897
     2898                return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
     2899        }
     2900
     2901        public static function APICPictureTypeLookup($index, $returnarray=false) {
     2902                static $APICPictureTypeLookup = array(
     2903                        0x00 => 'Other',
     2904                        0x01 => '32x32 pixels \'file icon\' (PNG only)',
     2905                        0x02 => 'Other file icon',
     2906                        0x03 => 'Cover (front)',
     2907                        0x04 => 'Cover (back)',
     2908                        0x05 => 'Leaflet page',
     2909                        0x06 => 'Media (e.g. label side of CD)',
     2910                        0x07 => 'Lead artist/lead performer/soloist',
     2911                        0x08 => 'Artist/performer',
     2912                        0x09 => 'Conductor',
     2913                        0x0A => 'Band/Orchestra',