diff --git a/lib/getid3/extension.cache.mysql.php b/lib/getid3/extension.cache.mysql.php index 2647584f9d8b766e0b0718900152db4d84bf86bc..a94eb9d94999abb669e99f291c879dd428de4e5a 100644 --- a/lib/getid3/extension.cache.mysql.php +++ b/lib/getid3/extension.cache.mysql.php @@ -134,7 +134,7 @@ class getID3_cached_mysql extends getID3 // public: analyze file - public function analyze($filename) { + public function analyze($filename, $filesize=null, $original_filename='') { if (file_exists($filename)) { @@ -157,7 +157,7 @@ class getID3_cached_mysql extends getID3 } // Miss - $analysis = parent::analyze($filename); + $analysis = parent::analyze($filename, $filesize, $original_filename); // Save result if (file_exists($filename)) { @@ -178,11 +178,11 @@ class getID3_cached_mysql extends getID3 private function create_table($drop=false) { $SQLquery = 'CREATE TABLE IF NOT EXISTS `'.mysql_real_escape_string($this->table).'` ('; - $SQLquery .= '`filename` VARCHAR(255) NOT NULL DEFAULT \'\''; + $SQLquery .= '`filename` VARCHAR(500) NOT NULL DEFAULT \'\''; $SQLquery .= ', `filesize` INT(11) NOT NULL DEFAULT \'0\''; $SQLquery .= ', `filetime` INT(11) NOT NULL DEFAULT \'0\''; $SQLquery .= ', `analyzetime` INT(11) NOT NULL DEFAULT \'0\''; - $SQLquery .= ', `value` TEXT NOT NULL'; + $SQLquery .= ', `value` LONGTEXT NOT NULL'; $SQLquery .= ', PRIMARY KEY (`filename`, `filesize`, `filetime`)) ENGINE=MyISAM'; $this->cursor = mysql_query($SQLquery, $this->connection); echo mysql_error($this->connection); diff --git a/lib/getid3/extension.cache.sqlite3.php b/lib/getid3/extension.cache.sqlite3.php index dd232390fe0afe7bc76e28e760c6eb57c3610fe6..92f3f2c7b3454212deef323e5b685310d3f1889b 100644 --- a/lib/getid3/extension.cache.sqlite3.php +++ b/lib/getid3/extension.cache.sqlite3.php @@ -49,20 +49,20 @@ * * sqlite3 table='getid3_cache', hide=false (PHP5) * - -*** database file will be stored in the same directory as this script, -*** webserver must have write access to that directory! -*** set $hide to TRUE to prefix db file with .ht to pervent access from web client -*** this is a default setting in the Apache configuration: - -# The following lines prevent .htaccess and .htpasswd files from being viewed by Web clients. - -<Files ~ "^\.ht"> - Order allow,deny - Deny from all - Satisfy all -</Files> - +* +* *** database file will be stored in the same directory as this script, +* *** webserver must have write access to that directory! +* *** set $hide to TRUE to prefix db file with .ht to pervent access from web client +* *** this is a default setting in the Apache configuration: +* +* The following lines prevent .htaccess and .htpasswd files from being viewed by Web clients. +* +* <Files ~ "^\.ht"> +* Order allow,deny +* Deny from all +* Satisfy all +* </Files> +* ******************************************************************************** * * ------------------------------------------------------------------- @@ -159,7 +159,7 @@ class getID3_cached_sqlite3 extends getID3 { * @param type $filename * @return boolean */ - public function analyze($filename) { + public function analyze($filename, $filesize=null, $original_filename='') { if (!file_exists($filename)) { return false; } @@ -182,7 +182,7 @@ class getID3_cached_sqlite3 extends getID3 { return unserialize(base64_decode($result)); } // if it hasn't been analyzed before, then do it now - $analysis = parent::analyze($filename); + $analysis = parent::analyze($filename, $filesize=null, $original_filename=''); // Save result $sql = $this->cache_file; $stmt = $db->prepare($sql); @@ -253,7 +253,8 @@ class getID3_cached_sqlite3 extends getID3 { return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, :filesize, :filetime, :atime, :val)"; break; case 'make_table': - return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) NOT NULL DEFAULT '', dirname VARCHAR(255) NOT NULL DEFAULT '', filesize INT(11) NOT NULL DEFAULT '0', filetime INT(11) NOT NULL DEFAULT '0', analyzetime INT(11) NOT NULL DEFAULT '0', val text not null, PRIMARY KEY (filename, filesize, filetime))"; + //return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) NOT NULL DEFAULT '', dirname VARCHAR(255) NOT NULL DEFAULT '', filesize INT(11) NOT NULL DEFAULT '0', filetime INT(11) NOT NULL DEFAULT '0', analyzetime INT(11) NOT NULL DEFAULT '0', val text not null, PRIMARY KEY (filename, filesize, filetime))"; + return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) DEFAULT '', dirname VARCHAR(255) DEFAULT '', filesize INT(11) DEFAULT '0', filetime INT(11) DEFAULT '0', analyzetime INT(11) DEFAULT '0', val text, PRIMARY KEY (filename, filesize, filetime))"; break; case 'get_cached_dir': return "SELECT val FROM $this->table WHERE dirname = :dirname"; diff --git a/lib/getid3/getid3.lib.php b/lib/getid3/getid3.lib.php index 10be0648af51bc4de684ecbc9b7ef783f891716e..1498b7cf03a020be21981e42f4a1f4253f77a9ad 100644 --- a/lib/getid3/getid3.lib.php +++ b/lib/getid3/getid3.lib.php @@ -414,6 +414,20 @@ class getid3_lib return $newarray; } + public static function flipped_array_merge_noclobber($array1, $array2) { + if (!is_array($array1) || !is_array($array2)) { + return false; + } + # naturally, this only works non-recursively + $newarray = array_flip($array1); + foreach (array_flip($array2) as $key => $val) { + if (!isset($newarray[$key])) { + $newarray[$key] = count($newarray); + } + } + return array_flip($newarray); + } + public static function ksort_recursive(&$theArray) { ksort($theArray); @@ -519,15 +533,14 @@ class getid3_lib } public static function XML2array($XMLstring) { - if (function_exists('simplexml_load_string')) { - if (function_exists('get_object_vars')) { - if (function_exists('libxml_disable_entity_loader')) { // (PHP 5 >= 5.2.11) - // http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html - libxml_disable_entity_loader(true); - } - $XMLobject = simplexml_load_string($XMLstring); - return self::SimpleXMLelement2array($XMLobject); - } + if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) { + // http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html + // https://core.trac.wordpress.org/changeset/29378 + $loader = libxml_disable_entity_loader(true); + $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT); + $return = self::SimpleXMLelement2array($XMLobject); + libxml_disable_entity_loader($loader); + return $return; } return false; } @@ -1166,6 +1179,11 @@ class getid3_lib fwrite($tmp, $imgData); fclose($tmp); $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); + if (($GetDataImageSize === false) || !isset($GetDataImageSize[0]) || !isset($GetDataImageSize[1])) { + return false; + } + $GetDataImageSize['height'] = $GetDataImageSize[0]; + $GetDataImageSize['width'] = $GetDataImageSize[1]; } unlink($tempfilename); } diff --git a/lib/getid3/getid3.php b/lib/getid3/getid3.php index a8844517cf8451f9a0ad9dcfe7b9b30a35340b77..7e52b0838ef4c2c1cc4ed7446cc8812a699d42af 100644 --- a/lib/getid3/getid3.php +++ b/lib/getid3/getid3.php @@ -28,7 +28,7 @@ $temp_dir = ini_get('upload_tmp_dir'); if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { $temp_dir = ''; } -if (!$temp_dir) { +if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts $temp_dir = sys_get_temp_dir(); } @@ -109,7 +109,7 @@ class getID3 protected $startup_error = ''; protected $startup_warning = ''; - const VERSION = '1.9.8-20140511'; + const VERSION = '1.9.10-20150914'; const FREAD_BUFFER_SIZE = 32768; const ATTACHMENTS_NONE = false; @@ -243,7 +243,7 @@ class getID3 } - public function openfile($filename) { + public function openfile($filename, $filesize=null) { try { if (!empty($this->startup_error)) { throw new getid3_exception($this->startup_error); @@ -256,7 +256,7 @@ class getID3 $this->filename = $filename; $this->info = array(); $this->info['GETID3_VERSION'] = $this->version(); - $this->info['php_memory_limit'] = $this->memory_limit; + $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); // remote files not supported if (preg_match('/^(ht|f)tp:\/\//', $filename)) { @@ -287,7 +287,7 @@ class getID3 throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); } - $this->info['filesize'] = filesize($filename); + $this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename)); // set redundant parameters - might be needed in some include file // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion $filename = str_replace('\\', '/', $filename); @@ -342,9 +342,9 @@ class getID3 } // public: analyze file - public function analyze($filename) { + public function analyze($filename, $filesize=null, $original_filename='') { try { - if (!$this->openfile($filename)) { + if (!$this->openfile($filename, $filesize)) { return $this->info; } @@ -389,7 +389,7 @@ class getID3 $formattest = fread($this->fp, 32774); // determine format - $determined_format = $this->GetFileFormat($formattest, $filename); + $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename)); // unable to determine file format if (!$determined_format) { @@ -1235,6 +1235,29 @@ class getID3 } } + // ID3v1 encoding detection hack start + // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets + // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess + if ($comment_name == 'id3v1') { + if ($encoding == 'ISO-8859-1') { + if (function_exists('iconv')) { + foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (preg_match('#^[\\x80-\\xFF]+$#', $value)) { + foreach (array('windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { + if (@iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) { + $encoding = $id3v1_bad_encoding; + break 3; + } + } + } + } + } + } + } + } + // ID3v1 encoding detection hack end + $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! } diff --git a/lib/getid3/module.archive.gzip.php b/lib/getid3/module.archive.gzip.php index c53c80fb28552ffbb033a8d0b97d424b42bf2dd8..fc84988ce04dc5cf440d3bb23ab126b2b41b815d 100644 --- a/lib/getid3/module.archive.gzip.php +++ b/lib/getid3/module.archive.gzip.php @@ -36,7 +36,7 @@ class getid3_gzip extends getid3_handler { //|ID1|ID2|CM |FLG| MTIME |XFL|OS | //+---+---+---+---+---+---+---+---+---+---+ - if ($info['filesize'] > $info['php_memory_limit']) { + if ($info['php_memory_limit'] && ($info['filesize'] > $info['php_memory_limit'])) { $info['error'][] = 'File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)'; return false; } @@ -56,7 +56,7 @@ class getid3_gzip extends getid3_handler { $attr = unpack($unpack_header, substr($buf, 0, $start_length)); if (!$this->get_os_type(ord($attr['os']))) { // Merge member with previous if wrong OS type - $arr_members[$i - 1] .= $buf; + $arr_members[($i - 1)] .= $buf; $arr_members[$i] = ''; $is_wrong_members = true; continue; diff --git a/lib/getid3/module.audio-video.flv.php b/lib/getid3/module.audio-video.flv.php index c5fbd4b0fc0124daa360a85e61be21b859754c45..2f86ebd917a5021102e9077242c2fe0c26e64aa3 100644 --- a/lib/getid3/module.audio-video.flv.php +++ b/lib/getid3/module.audio-video.flv.php @@ -541,7 +541,7 @@ class AMFReader { // Long string default: $value = '(unknown or unsupported data type)'; - break; + break; } return $value; diff --git a/lib/getid3/module.audio-video.matroska.php b/lib/getid3/module.audio-video.matroska.php index f2cc5ac0a7be126248fa80be4e84712e15ae35e3..097936028cefea9592e5e06b2c38413ce798f7ab 100644 --- a/lib/getid3/module.audio-video.matroska.php +++ b/lib/getid3/module.audio-video.matroska.php @@ -457,6 +457,7 @@ class getid3_matroska extends getid3_handler default: $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); + break; } $info['audio']['streams'][] = $track_info; @@ -524,6 +525,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('header', __LINE__, $element_data); + break; } unset($element_data['offset'], $element_data['end']); @@ -562,6 +564,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } + break; } if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required @@ -571,6 +574,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('seekhead', __LINE__, $seek_entry); + break; } } break; @@ -653,6 +657,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('track.video', __LINE__, $sub_subelement); + break; } } break; @@ -678,6 +683,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('track.audio', __LINE__, $sub_subelement); + break; } } break; @@ -713,6 +719,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); + break; } } break; @@ -736,24 +743,28 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); + break; } } break; default: $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); + break; } } break; default: $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); + break; } } break; default: $this->unhandledElement('track', __LINE__, $subelement); + break; } } @@ -762,6 +773,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('tracks', __LINE__, $track_entry); + break; } } break; @@ -825,6 +837,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); + break; } } $info_entry[$subelement['id_name']] = $chaptertranslate_entry; @@ -832,6 +845,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('info', __LINE__, $subelement); + break; } } $info['matroska']['info'][] = $info_entry; @@ -868,6 +882,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); + break; } } $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; @@ -879,6 +894,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); + break; } } $cues_entry[] = $cuepoint_entry; @@ -886,6 +902,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('cues', __LINE__, $subelement); + break; } } $info['matroska']['cues'] = $cues_entry; @@ -927,6 +944,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); + break; } } $tag_entry[$sub_subelement['id_name']] = $targets_entry; @@ -938,6 +956,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); + break; } } $tags_entry[] = $tag_entry; @@ -945,6 +964,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('tags', __LINE__, $subelement); + break; } } $info['matroska']['tags'] = $tags_entry; @@ -985,6 +1005,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); + break; } } $info['matroska']['attachments'][] = $attachedfile_entry; @@ -992,6 +1013,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('attachments', __LINE__, $subelement); + break; } } break; @@ -1051,6 +1073,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); + break; } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; @@ -1070,6 +1093,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); + break; } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; @@ -1077,6 +1101,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); + break; } } $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; @@ -1084,6 +1109,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); + break; } } $info['matroska']['chapters'][] = $editionentry_entry; @@ -1091,6 +1117,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('chapters', __LINE__, $subelement); + break; } } break; @@ -1119,6 +1146,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); + break; } } $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; @@ -1149,6 +1177,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); + break; } } $cluster_entry[$subelement['id_name']][] = $cluster_block_group; @@ -1160,6 +1189,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('cluster', __LINE__, $subelement); + break; } $this->current_offset = $subelement['end']; } @@ -1181,12 +1211,14 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('segment', __LINE__, $element_data); + break; } } break; default: $this->unhandledElement('root', __LINE__, $top_element); + break; } } } @@ -1339,6 +1371,7 @@ class getid3_matroska extends getid3_handler default: $this->unhandledElement('tag.simpletag', __LINE__, $element); + break; } } diff --git a/lib/getid3/module.audio-video.quicktime.php b/lib/getid3/module.audio-video.quicktime.php index 85b57cfb9d05b04918eed481ee081af5e3578e86..5e14e199f9a905f846d57544b69b5154d4845279 100644 --- a/lib/getid3/module.audio-video.quicktime.php +++ b/lib/getid3/module.audio-video.quicktime.php @@ -35,7 +35,7 @@ class getid3_quicktime extends getid3_handler $offset = 0; $atomcounter = 0; - + $atom_data_read_buffer_size = ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 2) : $this->getid3->option_fread_buffer_size * 1024); // allow [default: 32MB] if PHP configured with no memory_limit while ($offset < $info['avdataend']) { if (!getid3_lib::intValueSupported($offset)) { $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; @@ -68,7 +68,7 @@ class getid3_quicktime extends getid3_handler break; } $atomHierarchy = array(); - $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, round($this->getid3->memory_limit / 2))), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); + $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); $offset += $atomsize; $atomcounter++; @@ -120,6 +120,7 @@ class getid3_quicktime extends getid3_handler public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm + // https://code.google.com/p/mp4v2/wiki/iTunesMetadata $info = &$this->getid3->info; @@ -222,81 +223,88 @@ class getid3_quicktime extends getid3_handler break; + case "\xA9".'alb': // ALBum + case "\xA9".'ART': // + case "\xA9".'art': // ARTist + case "\xA9".'aut': // + case "\xA9".'cmt': // CoMmenT + case "\xA9".'com': // COMposer + case "\xA9".'cpy': // + case "\xA9".'day': // content created year + case "\xA9".'dir': // + case "\xA9".'ed1': // + case "\xA9".'ed2': // + case "\xA9".'ed3': // + case "\xA9".'ed4': // + case "\xA9".'ed5': // + case "\xA9".'ed6': // + case "\xA9".'ed7': // + case "\xA9".'ed8': // + case "\xA9".'ed9': // + case "\xA9".'enc': // + case "\xA9".'fmt': // + case "\xA9".'gen': // GENre + case "\xA9".'grp': // GRouPing + case "\xA9".'hst': // + case "\xA9".'inf': // + case "\xA9".'lyr': // LYRics + case "\xA9".'mak': // + case "\xA9".'mod': // + case "\xA9".'nam': // full NAMe + case "\xA9".'ope': // + case "\xA9".'PRD': // + case "\xA9".'prf': // + case "\xA9".'req': // + case "\xA9".'src': // + case "\xA9".'swr': // + case "\xA9".'too': // encoder + case "\xA9".'trk': // TRacK + case "\xA9".'url': // + case "\xA9".'wrn': // + case "\xA9".'wrt': // WRiTer + case '----': // itunes specific case 'aART': // Album ARTist + case 'akID': // iTunes store account type + case 'apID': // Purchase Account + case 'atID': // case 'catg': // CaTeGory + case 'cmID': // + case 'cnID': // case 'covr': // COVeR artwork case 'cpil': // ComPILation case 'cprt': // CoPyRighT case 'desc': // DESCription case 'disk': // DISK number case 'egid': // Episode Global ID + case 'geID': // case 'gnre': // GeNRE + case 'hdvd': // HD ViDeo case 'keyw': // KEYWord - case 'ldes': + case 'ldes': // Long DEScription case 'pcst': // PodCaST case 'pgap': // GAPless Playback + case 'plID': // case 'purd': // PURchase Date case 'purl': // Podcast URL - case 'rati': - case 'rndu': - case 'rpdu': + case 'rati': // + case 'rndu': // + case 'rpdu': // case 'rtng': // RaTiNG - case 'stik': + case 'sfID': // iTunes store country + case 'soaa': // SOrt Album Artist + case 'soal': // SOrt ALbum + case 'soar': // SOrt ARtist + case 'soco': // SOrt COmposer + case 'sonm': // SOrt NaMe + case 'sosn': // SOrt Show Name + case 'stik': // case 'tmpo': // TeMPO (BPM) case 'trkn': // TRacK Number + case 'tven': // tvEpisodeID case 'tves': // TV EpiSode case 'tvnn': // TV Network Name case 'tvsh': // TV SHow Name case 'tvsn': // TV SeasoN - case 'akID': // iTunes store account type - case 'apID': - case 'atID': - case 'cmID': - case 'cnID': - case 'geID': - case 'plID': - case 'sfID': // iTunes store country - case "\xA9".'alb': // ALBum - case "\xA9".'art': // ARTist - case "\xA9".'ART': - case "\xA9".'aut': - case "\xA9".'cmt': // CoMmenT - case "\xA9".'com': // COMposer - case "\xA9".'cpy': - case "\xA9".'day': // content created year - case "\xA9".'dir': - case "\xA9".'ed1': - case "\xA9".'ed2': - case "\xA9".'ed3': - case "\xA9".'ed4': - case "\xA9".'ed5': - case "\xA9".'ed6': - case "\xA9".'ed7': - case "\xA9".'ed8': - case "\xA9".'ed9': - case "\xA9".'enc': - case "\xA9".'fmt': - case "\xA9".'gen': // GENre - case "\xA9".'grp': // GRouPing - case "\xA9".'hst': - case "\xA9".'inf': - case "\xA9".'lyr': // LYRics - case "\xA9".'mak': - case "\xA9".'mod': - case "\xA9".'nam': // full NAMe - case "\xA9".'ope': - case "\xA9".'PRD': - case "\xA9".'prd': - case "\xA9".'prf': - case "\xA9".'req': - case "\xA9".'src': - case "\xA9".'swr': - case "\xA9".'too': // encoder - case "\xA9".'trk': // TRacK - case "\xA9".'url': - case "\xA9".'wrn': - case "\xA9".'wrt': // WRiTer - case '----': // itunes specific if ($atom_parent == 'udta') { // User data atom handler $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); @@ -361,17 +369,21 @@ class getid3_quicktime extends getid3_handler case 21: // tmpo/cpil flag switch ($atomname) { case 'cpil': + case 'hdvd': case 'pcst': case 'pgap': + // 8-bit integer (boolean) $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); break; case 'tmpo': + // 16-bit integer $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); break; case 'disk': case 'trkn': + // binary $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); $atom_structure['data'] = empty($num) ? '' : $num; @@ -379,21 +391,25 @@ class getid3_quicktime extends getid3_handler break; case 'gnre': + // enum $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); break; case 'rtng': + // 8-bit integer $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); break; case 'stik': + // 8-bit integer (enum) $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); break; case 'sfID': + // 32-bit integer $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); break; @@ -403,7 +419,17 @@ class getid3_quicktime extends getid3_handler $atom_structure['data'] = substr($boxdata, 8); break; + case 'plID': + // 64-bit integer + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); + + case 'atID': + case 'cnID': + case 'geID': + case 'tves': + case 'tvsn': default: + // 32-bit integer $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); } break; @@ -799,9 +825,9 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ //$FrameRateCalculatorArray = array(); $frames_count = 0; - $max_stts_entries_to_scan = min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']); + $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { - $info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($this->getid3->memory_limit / 1048576).'MB).'; + $info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).'; } for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); @@ -1399,7 +1425,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ case "\x00\x00\x00\x00": case 'meta': // METAdata atom // some kind of metacontainer, may contain a big data dump such as: - // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ data DE4.3.1 data DEiPhone 4 + // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); @@ -2111,8 +2137,18 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { static $handyatomtranslatorarray = array(); if (empty($handyatomtranslatorarray)) { + // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt + // http://atomicparsley.sourceforge.net/mpeg-4files.html + // https://code.google.com/p/mp4v2/wiki/iTunesMetadata + $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'ART'] = 'artist'; + $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'aut'] = 'author'; + $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'com'] = 'comment'; $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright'; - $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 $handyatomtranslatorarray["\xA9".'dir'] = 'director'; $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1'; $handyatomtranslatorarray["\xA9".'ed2'] = 'edit2'; @@ -2123,64 +2159,60 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br $handyatomtranslatorarray["\xA9".'ed7'] = 'edit7'; $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8'; $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9'; + $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by'; $handyatomtranslatorarray["\xA9".'fmt'] = 'format'; + $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 + $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; $handyatomtranslatorarray["\xA9".'inf'] = 'information'; + $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 + $handyatomtranslatorarray["\xA9".'mak'] = 'make'; + $handyatomtranslatorarray["\xA9".'mod'] = 'model'; + $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'ope'] = 'composer'; $handyatomtranslatorarray["\xA9".'prd'] = 'producer'; + $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; $handyatomtranslatorarray["\xA9".'prf'] = 'performers'; $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements'; $handyatomtranslatorarray["\xA9".'src'] = 'source_credit'; - $handyatomtranslatorarray["\xA9".'wrt'] = 'writer'; - - // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt - $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; - $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; - $handyatomtranslatorarray["\xA9".'mak'] = 'make'; - $handyatomtranslatorarray["\xA9".'mod'] = 'model'; - $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; $handyatomtranslatorarray["\xA9".'swr'] = 'software'; - $handyatomtranslatorarray["\xA9".'aut'] = 'author'; - $handyatomtranslatorarray["\xA9".'ART'] = 'artist'; + $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0 $handyatomtranslatorarray["\xA9".'trk'] = 'track'; - $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'com'] = 'comment'; - $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'ope'] = 'composer'; $handyatomtranslatorarray["\xA9".'url'] = 'url'; - $handyatomtranslatorarray["\xA9".'enc'] = 'encoder'; - - // http://atomicparsley.sourceforge.net/mpeg-4files.html - $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0 + $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; + $handyatomtranslatorarray["\xA9".'wrt'] = 'composer'; $handyatomtranslatorarray['aART'] = 'album_artist'; - $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 - $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 - $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'too'] = 'encoder'; // iTunes 4.0 - $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 - $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? - $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 - $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 - $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 - $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 - $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 - $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 - $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 - $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 - $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 - $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 - $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 - $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 - $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 - $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 - $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 - $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 - $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 - $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 - - // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt - - + $handyatomtranslatorarray['apID'] = 'purchase_account'; + $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 + $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 + $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 + $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? + $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 + $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 + $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 + $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['hdvd'] = 'hd_video'; // iTunes 4.0 + $handyatomtranslatorarray['ldes'] = 'description_long'; // + $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 + $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 + $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 + $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 + $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 + $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 + $handyatomtranslatorarray['soaa'] = 'sort_album_artist'; // + $handyatomtranslatorarray['soal'] = 'sort_album'; // + $handyatomtranslatorarray['soar'] = 'sort_artist'; // + $handyatomtranslatorarray['soco'] = 'sort_composer'; // + $handyatomtranslatorarray['sonm'] = 'sort_title'; // + $handyatomtranslatorarray['sosn'] = 'sort_show'; // + $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 + $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 + $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 + $handyatomtranslatorarray['tven'] = 'tv_episode_id'; // + $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 + $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 // boxnames: /* diff --git a/lib/getid3/module.audio.flac.php b/lib/getid3/module.audio.flac.php index e8f66c72f3d9a565202e1e93e6549e319edcd6c4..348cce328d22ff07e95cf24566e30368d40b4571 100644 --- a/lib/getid3/module.audio.flac.php +++ b/lib/getid3/module.audio.flac.php @@ -135,7 +135,17 @@ class getid3_flac extends getid3_handler if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) { foreach ($info['flac']['PICTURE'] as $entry) { if (!empty($entry['data'])) { - $info['flac']['comments']['picture'][] = array('image_mime'=>$entry['image_mime'], 'data'=>$entry['data']); + if (!isset($info['flac']['comments']['picture'])) { + $info['flac']['comments']['picture'] = array(); + } + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($entry[$picture_key])) { + $comments_picture_data[$picture_key] = $entry[$picture_key]; + } + } + $info['flac']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); } } } @@ -343,25 +353,25 @@ class getid3_flac extends getid3_handler $info = &$this->getid3->info; $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); - $picture['type'] = self::pictureTypeLookup($picture['typeid']); + $picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); $picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4))); $descr_length = getid3_lib::BigEndian2Int($this->fread(4)); if ($descr_length) { $picture['description'] = $this->fread($descr_length); } - $picture['width'] = getid3_lib::BigEndian2Int($this->fread(4)); - $picture['height'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['image_width'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['image_height'] = getid3_lib::BigEndian2Int($this->fread(4)); $picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4)); $picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4)); - $data_length = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['datalength'] = getid3_lib::BigEndian2Int($this->fread(4)); if ($picture['image_mime'] == '-->') { - $picture['data'] = $this->fread($data_length); + $picture['data'] = $this->fread($picture['datalength']); } else { $picture['data'] = $this->saveAttachment( - str_replace('/', '_', $picture['type']).'_'.$this->ftell(), + str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(), $this->ftell(), - $data_length, + $picture['datalength'], $picture['image_mime']); } diff --git a/lib/getid3/module.audio.mp3.php b/lib/getid3/module.audio.mp3.php index 329f7a675f4daf529ca537085df0d6fa1a444b9e..cba36193c22451c52be615f35a737fd23691b6c8 100644 --- a/lib/getid3/module.audio.mp3.php +++ b/lib/getid3/module.audio.mp3.php @@ -437,7 +437,6 @@ class getid3_mp3 extends getid3_handler // and $cc... is the audio data $head4 = substr($headerstring, 0, 4); - static $MPEGaudioHeaderDecodeCache = array(); if (isset($MPEGaudioHeaderDecodeCache[$head4])) { $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; @@ -648,9 +647,20 @@ class getid3_mp3 extends getid3_handler } //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { - if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + //if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + if (!empty($thisfile_mpeg_audio['VBR_frames'])) { + $used_filesize = 0; + if (!empty($thisfile_mpeg_audio['VBR_bytes'])) { + $used_filesize = $thisfile_mpeg_audio['VBR_bytes']; + } elseif (!empty($info['filesize'])) { + $used_filesize = $info['filesize']; + $used_filesize -= intval(@$info['id3v2']['headerlength']); + $used_filesize -= (isset($info['id3v1']) ? 128 : 0); + $used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0); + $info['warning'][] = 'MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes'; + } - $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']; + $framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames']; if ($thisfile_mpeg_audio['layer'] == '1') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 @@ -948,7 +958,7 @@ class getid3_mp3 extends getid3_handler } $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); if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { - $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion } break; diff --git a/lib/getid3/module.audio.ogg.php b/lib/getid3/module.audio.ogg.php index 94418bf6f89a59f25bffa6c7a9cc7af64a12aadc..3ebf8faa1711776c0dc0927dc021df4f8537887b 100644 --- a/lib/getid3/module.audio.ogg.php +++ b/lib/getid3/module.audio.ogg.php @@ -63,6 +63,12 @@ class getid3_ogg extends getid3_handler $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + } elseif (substr($filedata, 0, 8) == 'OpusHead') { + + if( $this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) == false ) { + return false; + } + } elseif (substr($filedata, 0, 8) == 'Speex ') { // http://www.speex.org/manual/node10.html @@ -255,7 +261,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get } else { - $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; + $info['error'][] = 'Expecting either "Speex ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; unset($info['ogg']); unset($info['mime_type']); return false; @@ -288,8 +294,19 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); $this->ParseVorbisComments(); break; - } + case 'opus': + $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags' + if(substr($filedata, 0, 8) != 'OpusTags') { + $info['error'][] = 'Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"'; + return false; + } + + $this->ParseVorbisComments(); + break; + + } // Last Page - Number of Samples if (!getid3_lib::intValueSupported($info['avdataend'])) { @@ -409,6 +426,57 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get return true; } + // http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 + public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + $info = &$this->getid3->info; + $info['audio']['dataformat'] = 'opus'; + $info['mime_type'] = 'audio/ogg; codecs=opus'; + + /** @todo find a usable way to detect abr (vbr that is padded to be abr) */ + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead' + $filedataoffset += 8; + $info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) { + $info['error'][] = 'Unknown opus version number (only accepting 1-15)'; + return false; + } + + $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) { + $info['error'][] = 'Invalid channel count in opus header (must not be zero)'; + return false; + } + + $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + + $info['ogg']['pageheader']['opus']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + //$filedataoffset += 2; + + //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + //$filedataoffset += 1; + + $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; + $info['opus']['sample_rate'] = $info['ogg']['pageheader']['opus']['sample_rate']; + $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; + + $info['audio']['channels'] = $info['opus']['out_channel_count']; + $info['audio']['sample_rate'] = $info['opus']['sample_rate']; + return true; + } + + public function ParseOggPageHeader() { // http://xiph.org/ogg/vorbis/doc/framing.html $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file @@ -471,6 +539,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get switch ($info['audio']['dataformat']) { case 'vorbis': case 'speex': + case 'opus': $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block $this->fseek($CommentStartOffset); $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; @@ -479,6 +548,10 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get if ($info['audio']['dataformat'] == 'vorbis') { $commentdataoffset += (strlen('vorbis') + 1); } + else if ($info['audio']['dataformat'] == 'opus') { + $commentdataoffset += strlen('OpusTags'); + } + break; case 'flac': @@ -489,6 +562,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get default: return false; + break; } $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); @@ -505,6 +579,12 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; for ($i = 0; $i < $CommentsCount; $i++) { + if ($i >= 10000) { + // https://github.com/owncloud/music/issues/212#issuecomment-43082336 + $info['warning'][] = 'Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'; + break; + } + $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { @@ -615,8 +695,12 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get $ogg = new self($this->getid3); $ogg->setStringMode($data); $info['ogg']['comments']['picture'][] = array( - 'image_mime' => $imageinfo['mime'], - 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), + 'image_mime' => $imageinfo['mime'], + 'datalength' => strlen($data), + 'picturetype' => 'cover art', + 'image_height' => $imageinfo['height'], + 'image_width' => $imageinfo['width'], + 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), ); unset($ogg); diff --git a/lib/getid3/module.tag.apetag.php b/lib/getid3/module.tag.apetag.php index 2fff68668468ca1460f4cba2ce98ca2c955625da..819e5e339be1665e8c24c07db6dba941e901764c 100644 --- a/lib/getid3/module.tag.apetag.php +++ b/lib/getid3/module.tag.apetag.php @@ -138,58 +138,88 @@ class getid3_apetag extends getid3_handler $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags); switch ($thisfile_ape_items_current['flags']['item_contents_raw']) { case 0: // UTF-8 - case 3: // Locator (URL, filename, etc), UTF-8 encoded - $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data'])); + case 2: // Locator (URL, filename, etc), UTF-8 encoded + $thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']); break; - default: // binary data + case 1: // binary data + default: break; } switch (strtolower($item_key)) { + // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain case 'replaygain_track_gain': - $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['track']['originator'] = 'unspecified'; + if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + } else { + $info['warning'][] = 'MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'replaygain_track_peak': - $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['track']['originator'] = 'unspecified'; - if ($thisfile_replaygain['track']['peak'] <= 0) { - $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + if ($thisfile_replaygain['track']['peak'] <= 0) { + $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + } else { + $info['warning'][] = 'MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; } break; case 'replaygain_album_gain': - $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['album']['originator'] = 'unspecified'; + if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + } else { + $info['warning'][] = 'MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'replaygain_album_peak': - $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['album']['originator'] = 'unspecified'; - if ($thisfile_replaygain['album']['peak'] <= 0) { - $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + if ($thisfile_replaygain['album']['peak'] <= 0) { + $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + } else { + $info['warning'][] = 'MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; } break; case 'mp3gain_undo': - list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); - $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); - $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); + $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); + $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + } else { + $info['warning'][] = 'MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'mp3gain_minmax': - list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); - $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); + $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + } else { + $info['warning'][] = 'MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'mp3gain_album_minmax': - list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); - $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); + $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + } else { + $info['warning'][] = 'MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'tracknumber': @@ -222,16 +252,24 @@ class getid3_apetag extends getid3_handler case 'cover art (recording)': case 'cover art (studio)': // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html + if (is_array($thisfile_ape_items_current['data'])) { + $info['warning'][] = 'APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8'; + $thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']); + } list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2); $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00"); $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']); - $thisfile_ape_items_current['image_mime'] = ''; - $imageinfo = array(); - $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo); - $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); - do { + $thisfile_ape_items_current['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo); + if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) { + $info['warning'][] = 'APEtag "'.$item_key.'" contains invalid image data'; + break; + } + $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + if ($this->inline_attachments === false) { // skip entirely unset($thisfile_ape_items_current['data']); @@ -269,7 +307,14 @@ class getid3_apetag extends getid3_handler if (!isset($info['ape']['comments']['picture'])) { $info['ape']['comments']['picture'] = array(); } - $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']); + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($thisfile_ape_items_current[$picture_key])) { + $comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key]; + } + } + $info['ape']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); } } while (false); break; @@ -317,7 +362,7 @@ class getid3_apetag extends getid3_handler public function parseAPEtagFlags($rawflagint) { // "Note: APE Tags 1.0 do not use any of the APE Tag flags. // All are set to zero on creation and ignored on reading." - // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html + // http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags $flags['header'] = (bool) ($rawflagint & 0x80000000); $flags['footer'] = (bool) ($rawflagint & 0x40000000); $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); diff --git a/lib/getid3/module.tag.id3v2.php b/lib/getid3/module.tag.id3v2.php index 28b30fa3e04ca2d4c461a615b808f5ff7b727f98..34058707d309a098f5c945c4208a296159388792 100644 --- a/lib/getid3/module.tag.id3v2.php +++ b/lib/getid3/module.tag.id3v2.php @@ -442,10 +442,14 @@ class getid3_id3v2 extends getid3_handler } // end footer if (isset($thisfile_id3v2['comments']['genre'])) { + $genres = array(); foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { - unset($thisfile_id3v2['comments']['genre'][$key]); - $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); + foreach ($this->ParseID3v2GenreString($value) as $genre) { + $genres[] = $genre; + } } + $thisfile_id3v2['comments']['genre'] = array_unique($genres); + unset($key, $value, $genres, $genre); } if (isset($thisfile_id3v2['comments']['track'])) { @@ -503,6 +507,13 @@ class getid3_id3v2 extends getid3_handler if (strpos($genrestring, "\x00") === false) { $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); } + + // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here: + // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name + $genrestring = str_replace('/', "\x00", $genrestring); + $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring); + $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring); + $genre_elements = explode("\x00", $genrestring); foreach ($genre_elements as $element) { $element = trim($element); @@ -625,12 +636,13 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -640,8 +652,8 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description)); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { @@ -717,11 +729,13 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -729,10 +743,10 @@ class getid3_id3v2 extends getid3_handler if (ord($frame_description) === 0) { $frame_description = ''; } - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } if ($frame_terminatorpos) { @@ -956,25 +970,26 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $parsedFrame['data'] = $parsedFrame['data']; $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['description'] = $frame_description; @@ -1002,8 +1017,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -1020,16 +1037,16 @@ class getid3_id3v2 extends getid3_handler $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); while (strlen($frame_remainingdata)) { $frame_offset = 0; - $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator); if ($frame_terminatorpos === false) { $frame_remainingdata = ''; } else { - if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); - $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { // timestamp probably omitted for first data item } else { @@ -1060,20 +1077,22 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); @@ -1330,8 +1349,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { @@ -1367,8 +1388,8 @@ class getid3_id3v2 extends getid3_handler if ($frame_offset >= $parsedFrame['datalength']) { $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); } else { - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1386,19 +1407,20 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['picturetypeid'] = $frame_picturetype; $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['datalength'] = strlen($parsedFrame['data']); $parsedFrame['image_mime'] = ''; $imageinfo = array(); - $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); - if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { - $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); - if ($imagechunkcheck[0]) { - $parsedFrame['image_width'] = $imagechunkcheck[0]; - } - if ($imagechunkcheck[1]) { - $parsedFrame['image_height'] = $imagechunkcheck[1]; + if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) { + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } } } @@ -1443,7 +1465,14 @@ class getid3_id3v2 extends getid3_handler if (!isset($info['id3v2']['comments']['picture'])) { $info['id3v2']['comments']['picture'] = array(); } - $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($parsedFrame[$picture_key])) { + $comments_picture_data[$picture_key] = $parsedFrame[$picture_key]; + } + } + $info['id3v2']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); } } } while (false); @@ -1462,8 +1491,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1472,25 +1503,25 @@ class getid3_id3v2 extends getid3_handler } $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_filename) === 0) { $frame_filename = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); $parsedFrame['encodingid'] = $frame_textencoding; @@ -1607,7 +1638,7 @@ class getid3_id3v2 extends getid3_handler } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information // There may be more than one 'LINK' frame in a tag, // but only one with the same contents // <Header for 'Linked information', ID: 'LINK'> @@ -1635,7 +1666,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']); } unset($parsedFrame['data']); @@ -1729,8 +1760,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); @@ -1752,25 +1785,25 @@ class getid3_id3v2 extends getid3_handler $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_sellername) === 0) { $frame_sellername = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1944,6 +1977,186 @@ class getid3_id3v2 extends getid3_handler unset($parsedFrame['data']); + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes) + // Element ID <text string> $00 + // Start time $xx xx xx xx + // End time $xx xx xx xx + // Start offset $xx xx xx xx + // End offset $xx xx xx xx + // <Optional embedded sub-frames> + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // <Optional embedded sub-frames> + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $info['warning'][] = 'CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'; + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { + if ($subframe['name'] == 'TIT2') { + $parsedFrame['chapter_name'] = $encoding_converted_text; + } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['chapter_description'] = $encoding_converted_text; + } + $parsedFrame['subframes'][] = $subframe; + } else { + $info['warning'][] = 'ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'; + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + } + + $id3v2_chapter_entry = array(); + foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) { + if (isset($parsedFrame[$id3v2_chapter_key])) { + $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; + } + } + if (!isset($info['id3v2']['chapters'])) { + $info['id3v2']['chapters'] = array(); + } + $info['id3v2']['chapters'][] = $id3v2_chapter_entry; + unset($id3v2_chapter_entry, $id3v2_chapter_key); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes) + // Element ID <text string> $00 + // CTOC flags %xx + // Entry count $xx + // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */ + // <Optional embedded sub-frames> + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + + $terminator_position = null; + for ($i = 0; $i < $parsedFrame['entry_count']; $i++) { + $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset); + $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset); + $frame_offset = $terminator_position + 1; + } + + $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01); + $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03); + + unset($ctoc_flags_raw, $terminator_position); + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // <Optional embedded sub-frames> + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $info['warning'][] = 'CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'; + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { + if ($subframe['name'] == 'TIT2') { + $parsedFrame['toc_name'] = $encoding_converted_text; + } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['toc_description'] = $encoding_converted_text; + } + $parsedFrame['subframes'][] = $subframe; + } else { + $info['warning'][] = 'ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'; + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + } + } return true; @@ -3344,7 +3557,7 @@ class getid3_id3v2 extends getid3_handler 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. 255 => "\x00\x00" ); - return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : ''); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00"); } public static function TextEncodingNameLookup($encoding) { diff --git a/lib/getid3/module.tag.lyrics3.php b/lib/getid3/module.tag.lyrics3.php index bf4bf8fb39598a69f97aabc512dc41f5c7d4fcc1..419888bf4f03501eb483da9aaf7d5f4ea1e40973 100644 --- a/lib/getid3/module.tag.lyrics3.php +++ b/lib/getid3/module.tag.lyrics3.php @@ -101,20 +101,24 @@ class getid3_lyrics3 extends getid3_handler $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); if (!isset($info['ape'])) { - $GETID3_ERRORARRAY = &$info['warning']; - getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_apetag = new getid3_apetag($getid3_temp); - $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; - $getid3_apetag->Analyze(); - if (!empty($getid3_temp->info['ape'])) { - $info['ape'] = $getid3_temp->info['ape']; - } - if (!empty($getid3_temp->info['replay_gain'])) { - $info['replay_gain'] = $getid3_temp->info['replay_gain']; + if (isset($info['lyrics3']['tag_offset_start'])) { + $GETID3_ERRORARRAY = &$info['warning']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_apetag = new getid3_apetag($getid3_temp); + $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; + $getid3_apetag->Analyze(); + if (!empty($getid3_temp->info['ape'])) { + $info['ape'] = $getid3_temp->info['ape']; + } + if (!empty($getid3_temp->info['replay_gain'])) { + $info['replay_gain'] = $getid3_temp->info['replay_gain']; + } + unset($getid3_temp, $getid3_apetag); + } else { + $info['warning'][] = 'Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)'; } - unset($getid3_temp, $getid3_apetag); } } diff --git a/lib/getid3/write.id3v2.php b/lib/getid3/write.id3v2.php index 4a6c9e9dd344bda4acca08ca4183dc7f2e1e409c..7b6eaa1d4ba45c25f0126ed46109dfa208dd8ecc 100644 --- a/lib/getid3/write.id3v2.php +++ b/lib/getid3/write.id3v2.php @@ -1543,6 +1543,9 @@ class getid3_write_id3v2 unset($frame_flags); $frame_data = false; if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array)) { + if(array_key_exists('description', $source_data_array) && array_key_exists('encodingid', $source_data_array) && array_key_exists('encoding', $this->tag_data)) { + $source_data_array['description'] = getid3_lib::iconv_fallback($this->tag_data['encoding'], $source_data_array['encoding'], $source_data_array['description']); + } if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array)) { $FrameUnsynchronisation = false; if ($this->majorversion >= 4) { @@ -1921,6 +1924,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB'; $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC'; $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK'; + $ID3v2ShortFrameNameLookup[2]['track_number'] = 'TRK'; $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI'; $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS'; $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1'; @@ -1941,6 +1945,7 @@ class getid3_write_id3v2 // The following are common to ID3v2.3 and ID3v2.4 $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC'; $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC'; + $ID3v2ShortFrameNameLookup[3]['picture'] = 'APIC'; $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM'; $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR'; $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR'; @@ -1988,6 +1993,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[3]['part_of_a_set'] = 'TPOS'; $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB'; $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK'; + $ID3v2ShortFrameNameLookup[3]['track_number'] = 'TRCK'; $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN'; $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO'; $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC'; diff --git a/paquet.xml b/paquet.xml index d478456dee7fe1d675ffb0536bc085a710315c41..ed43a82b90019f0ac4a5bb30e922ec511d11ff63 100644 --- a/paquet.xml +++ b/paquet.xml @@ -1,7 +1,7 @@ <paquet prefix="medias" categorie="multimedia" - version="2.10.24" + version="2.10.25" etat="stable" compatibilite="[3.0.0;3.1.*]" logo="prive/themes/spip/images/portfolio-32.png"