diff options
author | Paul Arthur <paul.arthur@flowerysong.com> | 2013-05-13 16:51:34 -0400 |
---|---|---|
committer | Paul Arthur <paul.arthur@flowerysong.com> | 2013-05-13 16:51:34 -0400 |
commit | 6bed09fbc9e5b18a925e48c7fc41f9ca29055c47 (patch) | |
tree | f368bdf30f46e486bc75b044030e9b2c2453098e | |
parent | 1d881ae0180766226229a8008f8b59362b5eaa23 (diff) | |
download | ampache-6bed09fbc9e5b18a925e48c7fc41f9ca29055c47.tar.gz ampache-6bed09fbc9e5b18a925e48c7fc41f9ca29055c47.tar.bz2 ampache-6bed09fbc9e5b18a925e48c7fc41f9ca29055c47.zip |
Bump getID3 version
-rwxr-xr-x | docs/CHANGELOG.md | 1 | ||||
-rw-r--r-- | modules/getid3/docs/changelog.txt | 30 | ||||
-rw-r--r-- | modules/getid3/docs/readme.txt | 3 | ||||
-rw-r--r-- | modules/getid3/getid3.lib.php | 30 | ||||
-rw-r--r-- | modules/getid3/getid3.php | 82 | ||||
-rw-r--r-- | modules/getid3/module.archive.gzip.php | 3 | ||||
-rw-r--r-- | modules/getid3/module.archive.szip.php | 16 | ||||
-rw-r--r-- | modules/getid3/module.archive.zip.php | 149 | ||||
-rw-r--r-- | modules/getid3/module.audio-video.matroska.php | 50 | ||||
-rw-r--r-- | modules/getid3/module.audio-video.riff.php | 268 | ||||
-rw-r--r-- | modules/getid3/module.audio.ac3.php | 35 | ||||
-rw-r--r-- | modules/getid3/module.audio.dss.php | 37 | ||||
-rw-r--r-- | modules/getid3/module.audio.dts.php | 140 | ||||
-rw-r--r-- | modules/getid3/module.audio.flac.php | 55 | ||||
-rw-r--r-- | modules/getid3/module.graphic.jpg.php | 3 |
15 files changed, 550 insertions, 352 deletions
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 40236cd8..f4dd5eb4 100755 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,7 @@ CHANGELOG 3.6-FUTURE ---------- +- Updated getID3 to 1.9.5 - Improved the performance of stream playlist creation (reported by AkbarSerad) - Fixed "Pure Random" / Random URLs (reported by mafe) diff --git a/modules/getid3/docs/changelog.txt b/modules/getid3/docs/changelog.txt index bbaedf10..ee8cd4f7 100644 --- a/modules/getid3/docs/changelog.txt +++ b/modules/getid3/docs/changelog.txt @@ -17,6 +17,36 @@ Version History =============== +1.9.5: [2013-02-20] James Heinrich, Dmitry Arkhipov + » DTS-in-WAV now properly supported + € DSS files return additional data in new keys, and some existing + keys have been renamed + * Bugfix: open_basedir not parsed correctly under Windows + (thanks yannick*jamontØgmail*com) + * Bugfix: [demo/demo.browse] might not display file or directory name + on PHP >=5.4.0 if filename not UTF-8 friendly + * Bugfix: [demo/demo.zip] could read more uncompressed data than + required; fail to read file if local data descriptor not set; + some wrong include files were listed; improved error message display + * Bugfix: [module.audio-video.riff] INFO comment chunks with null name + chunk not parsed correctly + * Bugfix: [module.archive.gz] gzip files with filename stored may have + filename reduplicated in [gzip][files] output + * Bugfix: [module.archive.zip] data_descriptor not parsed correctly + * Bugfix: [module.archive.zip] some newer compression methods unknown + * Bugfix: [module.archive.zip] not all flags parsed + * Bugfix: [module.archive.zip] local file header not parsed correctly + if file has zero values for compressed_size in Local File Header + * Bugfix: (#1493) better support for >2GB filesize on 32-bit Linux + * Bugfix: (#1474) unneccesary call to GetDataImageSize in JPEG module + * Bugfix: (#1470) GIF files falsely detected as TS format + * Bugfix: (#1431) Matroska did not parse PixelCrop* / DisplayUnit + (thanks jgerberØwikimedia*org) + * Bugfix: (#1430) split ID3v2 text values on null separator + * Bugfix: (#1426) MS Office 2007 file format now recognized as zip.msoffice + * Bugfix: (#1423) optimized CreateDeepArray function + * Bugfix: (#1415) add support for DS2 variant of DSS + 1.9.4b1: [2012-10-05] James Heinrich, Dmitry Arkhipov, Karl G. Holz » New module: extension.cache.sqlite3.php (by Karl G. Holz) » New demo: demos/getid3.demo.dirscan.php (by Karl G. Holz) diff --git a/modules/getid3/docs/readme.txt b/modules/getid3/docs/readme.txt index de960965..548ba643 100644 --- a/modules/getid3/docs/readme.txt +++ b/modules/getid3/docs/readme.txt @@ -66,6 +66,7 @@ Reads & parses (to varying degrees): * MP3/MP2/MP1 * MPC / Musepack * Ogg (Vorbis, OggFLAC, Speex) + * AAC / MP4 * AC3 * DTS * RealAudio @@ -99,7 +100,7 @@ Reads & parses (to varying degrees): * Matroska (MKV) * MPEG-1 / MPEG-2 * NSV (Nullsoft Streaming Video) - * Quicktime + * Quicktime (including MP4) * RealVideo € still image: diff --git a/modules/getid3/getid3.lib.php b/modules/getid3/getid3.lib.php index c5bfda04..08c3067d 100644 --- a/modules/getid3/getid3.lib.php +++ b/modules/getid3/getid3.lib.php @@ -481,9 +481,7 @@ class getid3_lib // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); // or // $foo['path']['to']['my'] = 'file.txt'; - while ($ArrayPath && ($ArrayPath{0} == $Separator)) { - $ArrayPath = substr($ArrayPath, 1); - } + $ArrayPath = ltrim($ArrayPath, $Separator); if (($pos = strpos($ArrayPath, $Separator)) !== false) { $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); } else { @@ -1150,7 +1148,7 @@ class getid3_lib if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { fwrite($tmp, $imgData); fclose($tmp); - $GetDataImageSize = @GetImageSize($tempfilename, $imageinfo); + $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); } unlink($tempfilename); } @@ -1317,4 +1315,28 @@ class getid3_lib return trim($string, "\x00"); } + public static function getFileSizeSyscall($path) { + $filesize = false; + + if (GETID3_OS_ISWINDOWS) { + if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: + $filesystem = new COM('Scripting.FileSystemObject'); + $file = $filesystem->GetFile($path); + $filesize = $file->Size(); + unset($filesystem, $file); + } else { + $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI'; + } + } else { + $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\''; + } + if (isset($commandline)) { + $output = trim(`$commandline`); + if (ctype_digit($output)) { + $filesize = (float) $output; + } + } + return $filesize; + } + } diff --git a/modules/getid3/getid3.php b/modules/getid3/getid3.php index 98bfb0a0..55eb8f03 100644 --- a/modules/getid3/getid3.php +++ b/modules/getid3/getid3.php @@ -9,6 +9,15 @@ // /// ///////////////////////////////////////////////////////////////// +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); +} +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); +} + // attempt to define temp dir as something flexible but reliable $temp_dir = ini_get('upload_tmp_dir'); if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { @@ -29,7 +38,7 @@ if ($open_basedir) { $temp_dir .= DIRECTORY_SEPARATOR; } $found_valid_tempdir = false; - $open_basedirs = explode(':', $open_basedir); + $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); foreach ($open_basedirs as $basedir) { if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { $basedir .= DIRECTORY_SEPARATOR; @@ -51,17 +60,6 @@ if (!$temp_dir) { define('GETID3_TEMP_DIR', $temp_dir); unset($open_basedir, $temp_dir); - -// define a constant rather than looking up every time it is needed -if (!defined('GETID3_OS_ISWINDOWS')) { - define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); -} - -// Get base path of getID3() - ONCE -if (!defined('GETID3_INCLUDEPATH')) { - define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); -} - // End: Defines @@ -105,7 +103,7 @@ class getID3 protected $startup_warning = ''; protected $memory_limit = 0; - const VERSION = '1.9.4b1-20121005'; + const VERSION = '1.9.5-20130220'; const FREAD_BUFFER_SIZE = 32768; const ATTACHMENTS_NONE = false; @@ -285,30 +283,8 @@ class getID3 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || ($this->info['filesize'] < 0) || (ftell($this->fp) < 0)) { - $real_filesize = false; - if (GETID3_OS_ISWINDOWS) { - if (class_exists('COM')) { - $filesystem = new COM('Scripting.FileSystemObject'); - $file = $filesystem->GetFile($this->info['filenamepath']); - $real_filesize = $file->Size(); - unset($filesystem, $file); - } else { - // From PHP 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: - $this->warning('COM class not available.'.(version_compare(PHP_VERSION, '5.4.5', '>=') ? ' COM and DOTNET support are no longer built into PHP since v5.4.5, please enable in php.ini' : '')); + $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); - $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; - $dir_output = `$commandline`; - if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; - } - } - } else { - $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($this->info['filenamepath']); - $dir_output = `$commandline`; - if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; - } - } if ($real_filesize === false) { unset($this->info['filesize']); fclose($this->fp); @@ -449,9 +425,6 @@ class getID3 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); } $class = new $class_name($this); - //if (!empty($determined_format['set_inline_attachments'])) { - // $class->inline_attachments = $this->option_save_attachments; - //} $class->Analyze(); unset($class); @@ -497,7 +470,7 @@ class getID3 } - // public: error handling + // private: error handling public function error($message) { $this->CleanUp(); if (!isset($this->info['error'])) { @@ -508,7 +481,7 @@ class getID3 } - // public: warning handling + // private: warning handling public function warning($message) { $this->info['warning'][] = $message; return true; @@ -633,7 +606,7 @@ class getID3 // DSS - audio - Digital Speech Standard 'dss' => array( - 'pattern' => '^[\x02-\x03]dss', + 'pattern' => '^[\x02-\x03]ds[s2]', 'group' => 'audio', 'module' => 'dss', 'mime_type' => 'application/octet-stream', @@ -653,7 +626,6 @@ class getID3 'group' => 'audio', 'module' => 'flac', 'mime_type' => 'audio/x-flac', - //'set_inline_attachments' => true, ), // LA - audio - Lossless Audio (LA) @@ -833,7 +805,6 @@ class getID3 'group' => 'audio-video', 'module' => 'matroska', 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska - //'set_inline_attachments' => true, ), // MPEG - audio/video - MPEG (Moving Pictures Experts Group) @@ -860,7 +831,6 @@ class getID3 'mime_type' => 'application/ogg', 'fail_id3' => 'WARNING', 'fail_ape' => 'WARNING', - //'set_inline_attachments' => true, ), // QT - audio/video - Quicktime @@ -898,7 +868,7 @@ class getID3 // TS - audio/video - MPEG-2 Transport Stream 'ts' => array( - 'pattern' => '^\x47', + 'pattern' => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern 'group' => 'audio-video', 'module' => 'ts', 'mime_type' => 'video/MP2T', @@ -1528,7 +1498,7 @@ class getID3 public function CalculateCompressionRatioAudio() { - if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) { + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { return false; } $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); @@ -1671,7 +1641,7 @@ abstract class getid3_handler return fread($this->getid3->fp, $bytes); } - protected function fseek($bytes, $whence = SEEK_SET) { + protected function fseek($bytes, $whence=SEEK_SET) { if ($this->data_string_flag) { switch ($whence) { case SEEK_SET: @@ -1766,7 +1736,7 @@ abstract class getid3_handler $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); $bytesleft = $length; while ($bytesleft > 0) { - if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false) { + if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); } $bytesleft -= $byteswritten; @@ -1777,17 +1747,23 @@ abstract class getid3_handler } - } catch(Exception $e) { + } catch (Exception $e) { - if (isset($fp_dest) && is_resource($fp_dest)) { // close and remove dest file if created + // close and remove dest file if created + if (isset($fp_dest) && is_resource($fp_dest)) { fclose($fp_dest); unlink($dest); } - $attachment = null; // do not set any is case of error + + // do not set any is case of error + $attachment = null; $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); - return false; } + + // seek to the end of attachment + $this->fseek($offset + $length); + return $attachment; } diff --git a/modules/getid3/module.archive.gzip.php b/modules/getid3/module.archive.gzip.php index f8818df5..182351f9 100644 --- a/modules/getid3/module.archive.gzip.php +++ b/modules/getid3/module.archive.gzip.php @@ -140,8 +140,9 @@ class getid3_gzip extends getid3_handler { //|...original file name, zero-terminated...| //+=========================================+ // GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz - $thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']); + $thisInfo['filename'] = preg_replace('#\\.gz$#i', '', $info['filename']); if ($thisInfo['flags']['filename']) { + $thisInfo['filename'] = ''; while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; diff --git a/modules/getid3/module.archive.szip.php b/modules/getid3/module.archive.szip.php index 8741e722..6f41d2f3 100644 --- a/modules/getid3/module.archive.szip.php +++ b/modules/getid3/module.archive.szip.php @@ -20,8 +20,8 @@ class getid3_szip extends getid3_handler public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $SZIPHeader = fread($this->getid3->fp, 6); + $this->fseek($info['avdataoffset']); + $SZIPHeader = $this->fread(6); if (substr($SZIPHeader, 0, 4) != "SZ\x0A\x04") { $info['error'][] = 'Expecting "53 5A 0A 04" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($SZIPHeader, 0, 4)).'"'; return false; @@ -29,20 +29,22 @@ class getid3_szip extends getid3_handler $info['fileformat'] = 'szip'; $info['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); $info['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); +$info['error'][] = 'SZIP parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; - while (!feof($this->getid3->fp)) { - $NextBlockID = fread($this->getid3->fp, 2); + while (!$this->feof()) { + $NextBlockID = $this->fread(2); switch ($NextBlockID) { case 'SZ': // Note that szip files can be concatenated, this has the same effect as // concatenating the files. this also means that global header blocks // might be present between directory/data blocks. - fseek($this->getid3->fp, 4, SEEK_CUR); + $this->fseek(4, SEEK_CUR); break; case 'BH': - $BHheaderbytes = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 3)); - $BHheaderdata = fread($this->getid3->fp, $BHheaderbytes); + $BHheaderbytes = getid3_lib::BigEndian2Int($this->fread(3)); + $BHheaderdata = $this->fread($BHheaderbytes); $BHheaderoffset = 0; while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) { //filename as \0 terminated string (empty string indicates end) diff --git a/modules/getid3/module.archive.zip.php b/modules/getid3/module.archive.zip.php index e846a20a..7756c56d 100644 --- a/modules/getid3/module.archive.zip.php +++ b/modules/getid3/module.archive.zip.php @@ -53,7 +53,8 @@ class getid3_zip extends getid3_handler $info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; $info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; - if ($centraldirectoryentry['uncompressed_size'] > 0) { + //if ($centraldirectoryentry['uncompressed_size'] > 0) { zero-byte files are valid + if (!empty($centraldirectoryentry['filename'])) { $info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); } } @@ -77,33 +78,54 @@ class getid3_zip extends getid3_handler $info['zip']['compression_speed'] = 'store'; } - return true; + // secondary check - we (should) already have all the info we NEED from the Central Directory above, but scanning each + // Local File Header entry will + foreach ($info['zip']['central_directory'] as $central_directory_entry) { + fseek($this->getid3->fp, $central_directory_entry['entry_offset'], SEEK_SET); + if ($fileentry = $this->ZIPparseLocalFileHeader()) { + $info['zip']['entries'][] = $fileentry; + } else { + $info['warning'][] = 'Error parsing Local File Header at offset '.$central_directory_entry['entry_offset']; + } + } + + if (!empty($info['zip']['files']['[Content_Types].xml']) &&
+ !empty($info['zip']['files']['_rels']['.rels']) &&
+ !empty($info['zip']['files']['docProps']['app.xml']) &&
+ !empty($info['zip']['files']['docProps']['core.xml'])) {
+ // http://technet.microsoft.com/en-us/library/cc179224.aspx + $info['fileformat'] = 'zip.msoffice';
+ if (!empty($ThisFileInfo['zip']['files']['ppt'])) {
+ $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
+ } elseif (!empty($ThisFileInfo['zip']['files']['xl'])) {
+ $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
+ } elseif (!empty($ThisFileInfo['zip']['files']['word'])) {
+ $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+ }
+ } + return true; } } } - if ($this->getZIPentriesFilepointer()) { - - // central directory couldn't be found and/or parsed - // scan through actual file data entries, recover as much as possible from probable trucated file - if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) { - $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)'; - } - $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; - foreach ($info['zip']['entries'] as $key => $valuearray) { - $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; - } - return true; - - } else { - + if (!$this->getZIPentriesFilepointer()) { unset($info['zip']); $info['fileformat'] = ''; $info['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; return false; + } + // central directory couldn't be found and/or parsed + // scan through actual file data entries, recover as much as possible from probable trucated file + if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) { + $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)'; + } + $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; + foreach ($info['zip']['entries'] as $key => $valuearray) { + $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; } + return true; } @@ -182,7 +204,7 @@ class getid3_zip extends getid3_handler $ZIPlocalFileHeader = fread($this->getid3->fp, 30); $LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4)); - if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { + if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { // "PK\x03\x04" // invalid Local File Header Signature fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly return false; @@ -218,17 +240,56 @@ class getid3_zip extends getid3_handler } } + if ($LocalFileHeader['compressed_size'] == 0) { + // *Could* be a zero-byte file + // But could also be a file written on the fly that didn't know compressed filesize beforehand. + // Correct compressed filesize should be in the data_descriptor located after this file data, and also in Central Directory (at end of zip file) + if (!empty($this->getid3->info['zip']['central_directory'])) { + foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) { + if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) { + if ($central_directory_entry['compressed_size'] > 0) { + // overwrite local zero value (but not ['raw']'compressed_size']) so that seeking for data_descriptor (and next file entry) works correctly + $LocalFileHeader['compressed_size'] = $central_directory_entry['compressed_size']; + } + break; + } + } + } + + } $LocalFileHeader['data_offset'] = ftell($this->getid3->fp); - //$LocalFileHeader['compressed_data'] = fread($this->getid3->fp, $LocalFileHeader['raw']['compressed_size']); - fseek($this->getid3->fp, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); + fseek($this->getid3->fp, $LocalFileHeader['compressed_size'], SEEK_CUR); // this should (but may not) match value in $LocalFileHeader['raw']['compressed_size'] -- $LocalFileHeader['compressed_size'] could have been overwritten above with value from Central Directory if ($LocalFileHeader['flags']['data_descriptor_used']) { - $DataDescriptor = fread($this->getid3->fp, 12); - $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); - $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); - $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); - } + $DataDescriptor = fread($this->getid3->fp, 16); + $LocalFileHeader['data_descriptor']['signature'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); + if ($LocalFileHeader['data_descriptor']['signature'] != 0x08074B50) { // "PK\x07\x08" + $this->getid3->warning[] = 'invalid Local File Header Data Descriptor Signature at offset '.(ftell($this->getid3->fp) - 16).' - expecting 08 07 4B 50, found '.getid3_lib::PrintHexBytes($LocalFileHeader['data_descriptor']['signature']); + fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + return false; + } + $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); + $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); + $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 12, 4)); + if (!$LocalFileHeader['raw']['compressed_size'] && $LocalFileHeader['data_descriptor']['compressed_size']) { + foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) { + if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) { + if ($LocalFileHeader['data_descriptor']['compressed_size'] == $central_directory_entry['compressed_size']) { + // $LocalFileHeader['compressed_size'] already set from Central Directory + } else { + $this->getid3->info['warning'][] = 'conflicting compressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['compressed_size'].') vs Central Directory ('.$central_directory_entry['compressed_size'].') for file at offset '.$LocalFileHeader['offset']; + } + if ($LocalFileHeader['data_descriptor']['uncompressed_size'] == $central_directory_entry['uncompressed_size']) { + $LocalFileHeader['uncompressed_size'] = $LocalFileHeader['data_descriptor']['uncompressed_size']; + } else { + $this->getid3->info['warning'][] = 'conflicting uncompressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['uncompressed_size'].') vs Central Directory ('.$central_directory_entry['uncompressed_size'].') for file at offset '.$LocalFileHeader['offset']; + } + break; + } + } + } + } return $LocalFileHeader; } @@ -317,7 +378,23 @@ class getid3_zip extends getid3_handler public static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { - $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); + // https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip-printable.html + $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); + // 0x0002 -- see below + // 0x0004 -- see below + $ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008); + $ParsedFlags['enhanced_deflation'] = (bool) ($flagbytes & 0x0010); + $ParsedFlags['compressed_patched_data'] = (bool) ($flagbytes & 0x0020); + $ParsedFlags['strong_encryption'] = (bool) ($flagbytes & 0x0040); + // 0x0080 - unused + // 0x0100 - unused + // 0x0200 - unused + // 0x0400 - unused + $ParsedFlags['language_encoding'] = (bool) ($flagbytes & 0x0800); + // 0x1000 - reserved + $ParsedFlags['mask_header_values'] = (bool) ($flagbytes & 0x2000); + // 0x4000 - reserved + // 0x8000 - reserved switch ($compressionmethod) { case 6: @@ -343,7 +420,6 @@ class getid3_zip extends getid3_handler } break; } - $ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008); return $ParsedFlags; } @@ -368,13 +444,16 @@ class getid3_zip extends getid3_handler 14 => 'VFAT', 15 => 'Alternate MVS', 16 => 'BeOS', - 17 => 'Tandem' + 17 => 'Tandem', + 18 => 'OS/400', + 19 => 'OS/X (Darwin)', ); return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]'); } public static function ZIPcompressionMethodLookup($index) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/ZIP.html static $ZIPcompressionMethodLookup = array( 0 => 'store', 1 => 'shrink', @@ -386,7 +465,19 @@ class getid3_zip extends getid3_handler 7 => 'tokenize', 8 => 'deflate', 9 => 'deflate64', - 10 => 'PKWARE Date Compression Library Imploding' + 10 => 'Imploded (old IBM TERSE)', + 11 => 'RESERVED[11]', + 12 => 'BZIP2', + 13 => 'RESERVED[13]', + 14 => 'LZMA (EFS)', + 15 => 'RESERVED[15]', + 16 => 'RESERVED[16]', + 17 => 'RESERVED[17]', + 18 => 'IBM TERSE (new)', + 19 => 'IBM LZ77 z Architecture (PFS)', + 96 => 'JPEG recompressed', + 97 => 'WavPack compressed', + 98 => 'PPMd version I, Rev 1', ); return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]'); diff --git a/modules/getid3/module.audio-video.matroska.php b/modules/getid3/module.audio-video.matroska.php index d1359f4f..3c1921fb 100644 --- a/modules/getid3/module.audio-video.matroska.php +++ b/modules/getid3/module.audio-video.matroska.php @@ -14,7 +14,6 @@ ///////////////////////////////////////////////////////////////// -// from: http://www.matroska.org/technical/specs/index.html define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>. @@ -207,11 +206,17 @@ define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. +/** +* @tutorial http://www.matroska.org/technical/specs/index.html +* +* @todo Rewrite EBML parser to reduce it's size and honor default element values +* @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection +*/ class getid3_matroska extends getid3_handler { // public options - public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] - public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] + public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] + public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] // private parser settings/placeholders private $EBMLbuffer = ''; @@ -263,10 +268,16 @@ class getid3_matroska extends getid3_handler case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; - if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } - if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } - if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } - if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); + $track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']); + $track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']); + + if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } + if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } + if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } + if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } + if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } + if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': @@ -996,19 +1007,7 @@ class getid3_matroska extends getid3_handler $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } } - //if (!empty($attachedfile_entry['FileData']) && !empty($attachedfile_entry['FileMimeType']) && preg_match('#^image/#i', $attachedfile_entry['FileMimeType'])) { - // if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { - // $attachedfile_entry['data'] = $attachedfile_entry['FileData']; - // $attachedfile_entry['image_mime'] = $attachedfile_entry['FileMimeType']; - // $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => $attachedfile_entry['FileName']); - // unset($attachedfile_entry['FileData'], $attachedfile_entry['FileMimeType']); - // } - //} - //if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { - // // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] - //} else { - $info['matroska']['attachments'][] = $attachedfile_entry; - //} + $info['matroska']['attachments'][] = $attachedfile_entry; break; default: @@ -1737,6 +1736,17 @@ class getid3_matroska extends getid3_handler return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); } + public static function displayUnit($value) { + // http://www.matroska.org/technical/specs/index.html#DisplayUnit + static $units = array( + 0 => 'pixels', + 1 => 'centimeters', + 2 => 'inches', + 3 => 'Display Aspect Ratio'); + + return (isset($units[$value]) ? $units[$value] : 'unknown'); + } + private static function getDefaultStreamInfo($streams) { foreach (array_reverse($streams) as $stream) { diff --git a/modules/getid3/module.audio-video.riff.php b/modules/getid3/module.audio-video.riff.php index d3b7b8cf..ab4b3b61 100644 --- a/modules/getid3/module.audio-video.riff.php +++ b/modules/getid3/module.audio-video.riff.php @@ -13,12 +13,18 @@ // Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX // // dependencies: module.audio.mp3.php // // module.audio.ac3.php // -// module.audio.dts.php (optional) // +// module.audio.dts.php // // /// ///////////////////////////////////////////////////////////////// +/** +* @todo Parse AC-3/DTS audio inside WAVE correctly +* @todo Rewrite RIFF parser totally +*/ + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true); class getid3_riff extends getid3_handler { @@ -76,64 +82,68 @@ class getid3_riff extends getid3_handler $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { - if (!getid3_lib::intValueSupported($nextRIFFoffset + 1024)) { - $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - break; - } else { + try { $this->fseek($nextRIFFoffset); - $nextRIFFheader = $this->fread(12); - if ($nextRIFFoffset == ($info['avdataend'] - 1)) { - if (substr($nextRIFFheader, 0, 1) == "\x00") { - // RIFF padded to WORD boundary, we're actually already at the end - break; - } + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + //$this->warning('RIFF parser: '.$e->getMessage()); + $this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong'); + $this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present'); + break; + } else { + throw $e; } - $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); - $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); - $nextRIFFtype = substr($nextRIFFheader, 8, 4); - $chunkdata = array(); - $chunkdata['offset'] = $nextRIFFoffset + 8; - $chunkdata['size'] = $nextRIFFsize; - $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; - switch ($nextRIFFheaderID) { - case 'RIFF': - $info['avdataend'] = $nextRIFFoffset; - if (!getid3_lib::intValueSupported($info['avdataend'])) { - $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - } - $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset); + } + $nextRIFFheader = $this->fread(12); + if ($nextRIFFoffset == ($info['avdataend'] - 1)) { + if (substr($nextRIFFheader, 0, 1) == "\x00") { + // RIFF padded to WORD boundary, we're actually already at the end + break; + } + } + $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); + $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); + $nextRIFFtype = substr($nextRIFFheader, 8, 4); + $chunkdata = array(); + $chunkdata['offset'] = $nextRIFFoffset + 8; + $chunkdata['size'] = $nextRIFFsize; + $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; - if (!isset($thisfile_riff[$nextRIFFtype])) { - $thisfile_riff[$nextRIFFtype] = array(); - } - $thisfile_riff[$nextRIFFtype][] = $chunkdata; - break; + switch ($nextRIFFheaderID) { - case 'JUNK': - // ignore - $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; - break; + case 'RIFF': + $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset); - case 'IDVX': - $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size'])); - break; + if (!isset($thisfile_riff[$nextRIFFtype])) { + $thisfile_riff[$nextRIFFtype] = array(); + } + $thisfile_riff[$nextRIFFtype][] = $chunkdata; + break; - default: - if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { - $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12); - if (substr($DIVXTAG, -7) == 'DIVXTAG') { - // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file - $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway'); - $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG); - break 2; - } + case 'JUNK': + // ignore + $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; + break; + + case 'IDVX': + $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size'])); + break; + + default: + if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { + $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12); + if (substr($DIVXTAG, -7) == 'DIVXTAG') { + // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file + $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway'); + $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG); + break 2; } - $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file'); - break 2; - } + } + $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file'); + break 2; + } + } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; @@ -178,7 +188,9 @@ class getid3_riff extends getid3_handler } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV) + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { @@ -460,6 +472,14 @@ class getid3_riff extends getid3_handler $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; } + if (!empty($info['dts'])) { + // Dolby DTS files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2001; + $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['dts']['bitrate']; + $thisfile_audio['sample_rate'] = $info['dts']['sample_rate']; + } break; case 0x08AE: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; @@ -1142,20 +1162,6 @@ class getid3_riff extends getid3_handler break; } - if (isset($thisfile_riff_raw['fmt ']['wFormatTag']) && ($thisfile_riff_raw['fmt ']['wFormatTag'] == 1)) { - // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html - $this->fseek($info['avdataoffset']); - $FirstFourBytes = $this->fread(4); - if (preg_match('/^\xFF\x1F\x00\xE8/s', $FirstFourBytes)) { - // DTSWAV - $thisfile_audio_dataformat = 'dts'; - } elseif (preg_match('/^\x7F\xFF\x80\x01/s', $FirstFourBytes)) { - // DTS, but this probably shouldn't happen - $thisfile_audio_dataformat = 'dts'; - } - } - - if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); } @@ -1283,9 +1289,11 @@ class getid3_riff extends getid3_handler $maxoffset = min($maxoffset, $info['avdataend']); while ($this->ftell() < $maxoffset) { $chunknamesize = $this->fread(8); - $chunkname = substr($chunknamesize, 0, 4); - $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); - if (strlen(trim($chunkname, "\x00")) < 4) { + //$chunkname = substr($chunknamesize, 0, 4); + $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult + $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); + //if (strlen(trim($chunkname, "\x00")) < 4) { + if (strlen($chunkname) < 4) { $this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.'); break; } @@ -1392,90 +1400,95 @@ class getid3_riff extends getid3_handler $info['avdataoffset'] = $this->ftell(); $info['avdataend'] = $info['avdataoffset'] + $chunksize; - $RIFFdataChunkContentsTest = $this->fread(36); - - if ((strlen($RIFFdataChunkContentsTest) > 0) && preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { + $testData = $this->fread(36); + if ($testData === '') { + break; + } + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) { // Probably is MP3 data - if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { + if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp); - $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); + $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false); if (empty($getid3_temp->info['error'])) { - $info['mpeg'] = $getid3_temp->info['mpeg']; $info['audio'] = $getid3_temp->info['audio']; + $info['mpeg'] = $getid3_temp->info['mpeg']; } unset($getid3_temp, $getid3_mp3); } - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 2) == getid3_ac3::syncword)) { + } elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { // This is probably AC-3 data $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + if ($isRegularAC3) { + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + } $getid3_ac3 = new getid3_ac3($getid3_temp); - $getid3_ac3->Analyze(); + if ($isRegularAC3) { + $getid3_ac3->Analyze(); + } else { + // Dolby Digital WAV + // AC-3 content, but not encoded in same format as normal AC-3 file + // For one thing, byte order is swapped + $ac3_data = ''; + for ($i = 0; $i < 28; $i += 2) { + $ac3_data .= substr($testData, 8 + $i + 1, 1); + $ac3_data .= substr($testData, 8 + $i + 0, 1); + } + $getid3_ac3->AnalyzeString($ac3_data); + } + if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['ac3'] = $getid3_temp->info['ac3']; - $info['warning'] = $getid3_temp->info['warning']; + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_ac3() says: ['.$newerror.']'); + } + } } unset($getid3_temp, $getid3_ac3); - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 8, 2) == getid3_ac3::syncword)) { - - // Dolby Digital WAV - // AC-3 content, but not encoded in same format as normal AC-3 file - // For one thing, byte order is swapped - if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { // ok to use tmpfile here - only 56 bytes - if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { - for ($i = 0; $i < 28; $i += 2) { - // swap byte order - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); - } - fclose($fd_temp); + } elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($RIFFtempfilename); - $getid3_temp->info['avdataend'] = 20; - $getid3_ac3 = new getid3_ac3($getid3_temp); - $getid3_ac3->Analyze(); - if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['ac3'] = $getid3_temp->info['ac3']; - $info['warning'] = $getid3_temp->info['warning']; - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): '.implode(';', $getid3_temp->info['error']); + // This is probably DTS data + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_dts = new getid3_dts($getid3_temp); + $getid3_dts->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['dts'] = $getid3_temp->info['dts']; + $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_dts() says: ['.$newerror.']'); } - unset($getid3_ac3, $getid3_temp); - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } - unlink($RIFFtempfilename); - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk')) { + unset($getid3_temp, $getid3_dts); + + } elseif (substr($testData, 0, 4) == 'wvpk') { // This is WavPack data - $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); - self::parseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); + $info['wavpack']['offset'] = $info['avdataoffset']; + $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($testData, 4, 4)); + $this->parseWavPackHeader(substr($testData, 8, 28)); } else { - // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it - } - $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; + $nextoffset = $info['avdataend']; $this->fseek($nextoffset); break; @@ -1628,7 +1641,8 @@ class getid3_riff extends getid3_handler 'ISTR'=>'starring', 'ITCH'=>'encoded_by', 'IWEB'=>'url', - 'IWRI'=>'writer' + 'IWRI'=>'writer', + '____'=>'comment', ); foreach ($RIFFinfoKeyLookup as $key => $value) { if (isset($RIFFinfoArray[$key])) { @@ -1787,8 +1801,8 @@ class getid3_riff extends getid3_handler 19 => 'Sci Fi', 20 => 'Thriller', 21 => 'Western', - ); - static $DIVXTAGrating = array( + ), + $DIVXTAGrating = array( 0 => 'Unrated', 1 => 'G', 2 => 'PG', @@ -1818,6 +1832,10 @@ class getid3_riff extends getid3_handler } } + foreach ($parsed as $tag => $value) { + $parsed[$tag] = array($value); + } + return $parsed; } diff --git a/modules/getid3/module.audio.ac3.php b/modules/getid3/module.audio.ac3.php index be3064f1..9834feb5 100644 --- a/modules/getid3/module.audio.ac3.php +++ b/modules/getid3/module.audio.ac3.php @@ -272,20 +272,17 @@ class getid3_ac3 extends getid3_handler } public static function audioCodingModeLookup($acmod) { - static $audioCodingModeLookup = array(); - if (empty($audioCodingModeLookup)) { - // array(channel configuration, # channels (not incl LFE), channel order) - $audioCodingModeLookup = array ( - 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), - 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), - 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), - 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), - 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), - 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), - 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), - 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR') - ); - } + // array(channel configuration, # channels (not incl LFE), channel order) + static $audioCodingModeLookup = array ( + 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), + 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), + 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), + 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), + 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), + 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), + 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), + 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), + ); return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); } @@ -326,7 +323,7 @@ class getid3_ac3 extends getid3_handler } public static function channelsEnabledLookup($acmod, $lfeon) { - $channelsEnabledLookup = array( + $lookup = array( 'ch1'=>(bool) ($acmod == 0), 'ch2'=>(bool) ($acmod == 0), 'left'=>(bool) ($acmod > 1), @@ -339,15 +336,15 @@ class getid3_ac3 extends getid3_handler switch ($acmod) { case 4: case 5: - $channelsEnabledLookup['surround_mono'] = true; + $lookup['surround_mono'] = true; break; case 6: case 7: - $channelsEnabledLookup['surround_left'] = true; - $channelsEnabledLookup['surround_right'] = true; + $lookup['surround_left'] = true; + $lookup['surround_right'] = true; break; } - return $channelsEnabledLookup; + return $lookup; } public static function heavyCompression($compre) { diff --git a/modules/getid3/module.audio.dss.php b/modules/getid3/module.audio.dss.php index 5e1ceacb..4719d53b 100644 --- a/modules/getid3/module.audio.dss.php +++ b/modules/getid3/module.audio.dss.php @@ -23,36 +23,41 @@ class getid3_dss extends getid3_handler fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $DSSheader = fread($this->getid3->fp, 1256); - if (!preg_match('#^(\x02|\x03)dss#', $DSSheader)) { - $info['error'][] = 'Expecting "[02-03] 64 73 73" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"'; + if (!preg_match('#^(\x02|\x03)ds[s2]#', $DSSheader)) { + $info['error'][] = 'Expecting "[02-03] 64 73 [73|32]" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"'; return false; } // some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm - - // shortcut + $info['encoding'] = 'ISO-8859-1'; // not certain, but assumed $info['dss'] = array(); - $thisfile_dss = &$info['dss']; $info['fileformat'] = 'dss'; - $info['audio']['dataformat'] = 'dss'; + $info['mime_type'] = 'audio/x-'.substr($DSSheader, 1, 3); // "audio/x-dss" or "audio/x-ds2" + $info['audio']['dataformat'] = substr($DSSheader, 1, 3); // "dss" or "ds2" $info['audio']['bitrate_mode'] = 'cbr'; - //$thisfile_dss['encoding'] = 'ISO-8859-1'; - - $thisfile_dss['version'] = ord(substr($DSSheader, 0, 1)); - $thisfile_dss['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12)); - $thisfile_dss['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12)); - //$thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); // I thought time was in seconds, it's actually HHMMSS - $thisfile_dss['length'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2)); - $thisfile_dss['priority'] = ord(substr($DSSheader, 793, 1)); - $thisfile_dss['comments'] = trim(substr($DSSheader, 798, 100)); + $info['dss']['version'] = ord(substr($DSSheader, 0, 1)); + $info['dss']['hardware'] = trim(substr($DSSheader, 12, 16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400" + $info['dss']['unknown1'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 28, 4)); + // 32-37 = "FE FF FE FF F7 FF" in all the sample files I've seen + $info['dss']['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12)); + $info['dss']['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12)); + $info['dss']['playtime_sec'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2)); // approximate file playtime in HHMMSS + $info['dss']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 512, 4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512 + $info['dss']['priority'] = ord(substr($DSSheader, 793, 1)); + $info['dss']['comments'] = trim(substr($DSSheader, 798, 100)); //$info['audio']['bits_per_sample'] = ?; //$info['audio']['sample_rate'] = ?; $info['audio']['channels'] = 1; - $info['playtime_seconds'] = $thisfile_dss['length']; + $info['playtime_seconds'] = $info['dss']['playtime_ms'] / 1000; + if (floor($info['dss']['playtime_ms'] / 1000) != $info['dss']['playtime_sec']) { + // *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check + $info['playtime_seconds'] = $info['dss']['playtime_sec']; + $this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value'); + } $info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds']; return true; diff --git a/modules/getid3/module.audio.dts.php b/modules/getid3/module.audio.dts.php index 5b910940..79982ccc 100644 --- a/modules/getid3/module.audio.dts.php +++ b/modules/getid3/module.audio.dts.php @@ -14,64 +14,102 @@ ///////////////////////////////////////////////////////////////// +/** +* @tutorial http://wiki.multimedia.cx/index.php?title=DTS +*/ class getid3_dts extends getid3_handler { + /** + * Default DTS syncword used in native .cpt or .dts formats + */ const syncword = "\x7F\xFE\x80\x01"; - public function Analyze() { - $info = &$this->getid3->info; + private $readBinDataOffset = 0; - // Specs taken from "DTS Coherent Acoustics;Core and Extensions, ETSI TS 102 114 V1.2.1 (2002-12)" - // (http://pda.etsi.org/pda/queryform.asp) - // With thanks to Gambit <macteam@users.sourceforge.net> http://mac.sourceforge.net/atl/ + /** + * Possible syncwords indicating bitstream encoding + */ + public static $syncwords = array( + 0 => "\x7F\xFE\x80\x01", // raw big-endian + 1 => "\xFE\x7F\x01\x80", // raw little-endian + 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian + 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian + public function Analyze() { + $info = &$this->getid3->info; $info['fileformat'] = 'dts'; $this->fseek($info['avdataoffset']); - $DTSheader = $this->fread(16); - - if (strpos($DTSheader, self::syncword) === 0) { - $info['dts']['raw']['magic'] = self::syncword; - $offset = 4; - } else { - if (!$this->isDependencyFor('matroska')) { - unset($info['fileformat']); - return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DTSheader, 0, 4)).'"'); + $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes + + // check syncword + $sync = substr($DTSheader, 0, 4); + if (($encoding = array_search($sync, self::$syncwords)) !== false) { + + $info['dts']['raw']['magic'] = $sync; + $this->readBinDataOffset = 32; + + } elseif ($this->isDependencyFor('matroska')) { + + // Matroska contains DTS without syncword encoded as raw big-endian format + $encoding = 0; + $this->readBinDataOffset = 0; + + } else { + + unset($info['fileformat']); + return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"'); + + } + + // decode header + $fhBS = ''; + for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) { + switch ($encoding) { + case 0: // raw big-endian + $fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ); + break; + case 1: // raw little-endian + $fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))); + break; + case 2: // 14-bit big-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14); + break; + case 3: // 14-bit little-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14); + break; } - $offset = 0; } - $fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, $offset, 12)); - $bsOffset = 0; - $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5); - $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7); - $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14); - $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6); - $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4); - $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5); - $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3); - $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1); + $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7); + $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14); + $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6); + $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3); + $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1); if ($info['dts']['flags']['crc_present']) { - $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16); + $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16); } - $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4); - $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4); + $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2); + $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4); $info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']); @@ -90,16 +128,20 @@ class getid3_dts extends getid3_handler $info['audio']['sample_rate'] = $info['dts']['sample_rate']; $info['audio']['channels'] = $info['dts']['channels']; $info['audio']['bitrate'] = $info['dts']['bitrate']; - if (isset($info['avdataend'])) { + if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8); + if (($encoding == 2) || ($encoding == 3)) { + // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate + $info['playtime_seconds'] *= (14 / 16); + } } - return true; } - private function readBinData($bin, &$offset, $length) { - $data = substr($bin, $offset, $length); - $offset += $length; + private function readBinData($bin, $length) { + $data = substr($bin, $this->readBinDataOffset, $length); + $this->readBinDataOffset += $length; + return bindec($data); } diff --git a/modules/getid3/module.audio.flac.php b/modules/getid3/module.audio.flac.php index ec8136fe..6b9598c7 100644 --- a/modules/getid3/module.audio.flac.php +++ b/modules/getid3/module.audio.flac.php @@ -16,6 +16,9 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); +/** +* @tutorial http://flac.sourceforge.net/format.html +*/ class getid3_flac extends getid3_handler { const syncword = 'fLaC'; @@ -23,8 +26,6 @@ class getid3_flac extends getid3_handler public function Analyze() { $info = &$this->getid3->info; - // http://flac.sourceforge.net/format.html - $this->fseek($info['avdataoffset']); $StreamMarker = $this->fread(4); if ($StreamMarker != self::syncword) { @@ -146,7 +147,7 @@ class getid3_flac extends getid3_handler if ($info['flac']['uncompressed_audio_bytes'] == 0) { return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero'); } - if (!$this->isDependencyFor('matroska')) { + if (!empty($info['flac']['compressed_audio_bytes'])) { $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; } } @@ -357,7 +358,7 @@ class getid3_flac extends getid3_handler $picture['data'] = $this->fread($data_length); } else { $picture['data'] = $this->saveAttachment( - $picture['type'].'_'.$this->ftell(), + str_replace('/', '_', $picture['type']).'_'.$this->ftell(), $this->ftell(), $data_length, $picture['image_mime']); @@ -384,30 +385,30 @@ class getid3_flac extends getid3_handler public static function applicationIDLookup($applicationid) { // http://flac.sourceforge.net/id.html static $lookup = array( - 0x41544348 => 'FlacFile', // "ATCH"
- 0x42534F4C => 'beSolo', // "BSOL"
- 0x42554753 => 'Bugs Player', // "BUGS"
- 0x43756573 => 'GoldWave cue points (specification)', // "Cues"
- 0x46696361 => 'CUE Splitter', // "Fica"
- 0x46746F6C => 'flac-tools', // "Ftol"
- 0x4D4F5442 => 'MOTB MetaCzar', // "MOTB"
- 0x4D505345 => 'MP3 Stream Editor', // "MPSE"
- 0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML"
- 0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF"
- 0x5346464C => 'Sound Font FLAC', // "SFFL"
- 0x534F4E59 => 'Sony Creative Software', // "SONY"
- 0x5351455A => 'flacsqueeze', // "SQEZ"
- 0x54745776 => 'TwistedWave', // "TtWv"
- 0x55495453 => 'UITS Embedding tools', // "UITS"
- 0x61696666 => 'FLAC AIFF chunk storage', // "aiff"
- 0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag"
- 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem"
- 0x71667374 => 'QFLAC Studio', // "qfst"
- 0x72696666 => 'FLAC RIFF chunk storage', // "riff"
- 0x74756E65 => 'TagTuner', // "tune"
- 0x78626174 => 'XBAT', // "xbat"
+ 0x41544348 => 'FlacFile', // "ATCH" + 0x42534F4C => 'beSolo', // "BSOL" + 0x42554753 => 'Bugs Player', // "BUGS" + 0x43756573 => 'GoldWave cue points (specification)', // "Cues" + 0x46696361 => 'CUE Splitter', // "Fica" + 0x46746F6C => 'flac-tools', // "Ftol" + 0x4D4F5442 => 'MOTB MetaCzar', // "MOTB" + 0x4D505345 => 'MP3 Stream Editor', // "MPSE" + 0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML" + 0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF" + 0x5346464C => 'Sound Font FLAC', // "SFFL" + 0x534F4E59 => 'Sony Creative Software', // "SONY" + 0x5351455A => 'flacsqueeze', // "SQEZ" + 0x54745776 => 'TwistedWave', // "TtWv" + 0x55495453 => 'UITS Embedding tools', // "UITS" + 0x61696666 => 'FLAC AIFF chunk storage', // "aiff" + 0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag" + 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem" + 0x71667374 => 'QFLAC Studio', // "qfst" + 0x72696666 => 'FLAC RIFF chunk storage', // "riff" + 0x74756E65 => 'TagTuner', // "tune" + 0x78626174 => 'XBAT', // "xbat" 0x786D6364 => 'xmcd', // "xmcd" - );
+ ); return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); } diff --git a/modules/getid3/module.graphic.jpg.php b/modules/getid3/module.graphic.jpg.php index 5663a2f5..3db65438 100644 --- a/modules/getid3/module.graphic.jpg.php +++ b/modules/getid3/module.graphic.jpg.php @@ -31,7 +31,8 @@ class getid3_jpg extends getid3_handler fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $imageinfo = array(); - list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo); + //list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo); + list($width, $height, $type) = getimagesize($info['filenamepath'], $imageinfo); // http://www.getid3.org/phpBB3/viewtopic.php?t=1474 if (isset($imageinfo['APP13'])) { |