External Libraries: Update getID3 to version 1.9.21.

The latest version includes preliminary PHP 8.1 support, as well as a variety of bug fixes.

Release notes: https://github.com/JamesHeinrich/getID3/releases/tag/v1.9.21

A full list of changes in this update can be found on GitHub:
https://github.com/JamesHeinrich/getID3/compare/v1.9.20...v1.9.21

This commit also includes:
* Setting the `$options_audiovideo_quicktime_ReturnAtomData` property (now `false` by default) to `true` in `wp_read_video_metadata()` and `wp_read_audio_metadata()` in order to get the `created_timestamp` value.
* PHPCS adjustments previously made for a passing PHP Compatibility scan.

Follow-up to [47601], [47737], [47902], [48278], [49621], [50714].

Props jrf, SergeyBiryukov.
Fixes #54162.
Built from https://develop.svn.wordpress.org/trunk@52254


git-svn-id: http://core.svn.wordpress.org/trunk@51846 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Sergey Biryukov 2021-11-26 03:06:03 +00:00
parent 904c4a8b5c
commit 70f43f261b
17 changed files with 800 additions and 529 deletions

View File

@ -3559,6 +3559,9 @@ function wp_read_video_metadata( $file ) {
} }
$id3 = new getID3(); $id3 = new getID3();
// Required to get the `created_timestamp` value.
$id3->options_audiovideo_quicktime_ReturnAtomData = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
$data = $id3->analyze( $file ); $data = $id3->analyze( $file );
if ( isset( $data['video']['lossless'] ) ) { if ( isset( $data['video']['lossless'] ) ) {
@ -3670,6 +3673,9 @@ function wp_read_audio_metadata( $file ) {
} }
$id3 = new getID3(); $id3 = new getID3();
// Required to get the `created_timestamp` value.
$id3->options_audiovideo_quicktime_ReturnAtomData = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
$data = $id3->analyze( $file ); $data = $id3->analyze( $file );
if ( ! empty( $data['audio'] ) ) { if ( ! empty( $data['audio'] ) ) {

View File

@ -242,7 +242,7 @@ class getid3_lib
/** /**
* ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
* *
* @link http://www.psc.edu/general/software/packages/ieee/ieee.html * @link https://web.archive.org/web/20120325162206/http://www.psc.edu/general/software/packages/ieee/ieee.php
* @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
* *
* @param string $byteword * @param string $byteword
@ -294,12 +294,12 @@ class getid3_lib
if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
// Not a Number // Not a Number
$floatvalue = false; $floatvalue = NAN;
} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
if ($signbit == '1') { if ($signbit == '1') {
$floatvalue = '-infinity'; $floatvalue = -INF;
} else { } else {
$floatvalue = '+infinity'; $floatvalue = INF;
} }
} elseif (($exponent == 0) && ($fraction == 0)) { } elseif (($exponent == 0) && ($fraction == 0)) {
if ($signbit == '1') { if ($signbit == '1') {
@ -427,14 +427,20 @@ class getid3_lib
* @return string * @return string
*/ */
public static function Dec2Bin($number) { public static function Dec2Bin($number) {
if (!is_numeric($number)) {
// https://github.com/JamesHeinrich/getID3/issues/299
trigger_error('TypeError: Dec2Bin(): Argument #1 ($number) must be numeric, '.gettype($number).' given', E_USER_WARNING);
return '';
}
$bytes = array();
while ($number >= 256) { while ($number >= 256) {
$bytes[] = (($number / 256) - (floor($number / 256))) * 256; $bytes[] = (int) (($number / 256) - (floor($number / 256))) * 256;
$number = floor($number / 256); $number = floor($number / 256);
} }
$bytes[] = $number; $bytes[] = (int) $number;
$binstring = ''; $binstring = '';
for ($i = 0; $i < count($bytes); $i++) { foreach ($bytes as $i => $byte) {
$binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; $binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
} }
return $binstring; return $binstring;
} }
@ -665,6 +671,7 @@ class getid3_lib
// or // or
// $foo['path']['to']['my'] = 'file.txt'; // $foo['path']['to']['my'] = 'file.txt';
$ArrayPath = ltrim($ArrayPath, $Separator); $ArrayPath = ltrim($ArrayPath, $Separator);
$ReturnedArray = array();
if (($pos = strpos($ArrayPath, $Separator)) !== false) { if (($pos = strpos($ArrayPath, $Separator)) !== false) {
$ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
} else { } else {
@ -1538,12 +1545,21 @@ class getid3_lib
public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) { public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) {
// Copy all entries from ['tags'] into common ['comments'] // Copy all entries from ['tags'] into common ['comments']
if (!empty($ThisFileInfo['tags'])) { if (!empty($ThisFileInfo['tags'])) {
if (isset($ThisFileInfo['tags']['id3v1'])) {
// Some tag types can only support limited character sets and may contain data in non-standard encoding (usually ID3v1)
// and/or poorly-transliterated tag values that are also in tag formats that do support full-range character sets
// To make the output more user-friendly, process the potentially-problematic tag formats last to enhance the chance that
// the first entries in [comments] are the most correct and the "bad" ones (if any) come later.
// https://github.com/JamesHeinrich/getID3/issues/338
$processLastTagTypes = array('id3v1','riff');
foreach ($processLastTagTypes as $processLastTagType) {
if (isset($ThisFileInfo['tags'][$processLastTagType])) {
// bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings // bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
$ID3v1 = $ThisFileInfo['tags']['id3v1']; $temp = $ThisFileInfo['tags'][$processLastTagType];
unset($ThisFileInfo['tags']['id3v1']); unset($ThisFileInfo['tags'][$processLastTagType]);
$ThisFileInfo['tags']['id3v1'] = $ID3v1; $ThisFileInfo['tags'][$processLastTagType] = $temp;
unset($ID3v1); unset($temp);
}
} }
foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
foreach ($tagarray as $tagname => $tagdata) { foreach ($tagarray as $tagname => $tagdata) {
@ -1562,7 +1578,7 @@ class getid3_lib
// new value is identical but shorter-than (or equal-length to) one already in comments - skip // new value is identical but shorter-than (or equal-length to) one already in comments - skip
break 2; break 2;
} }
}
if (function_exists('mb_convert_encoding')) { if (function_exists('mb_convert_encoding')) {
if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) { if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) {
// value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1. // value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1.
@ -1570,12 +1586,22 @@ class getid3_lib
break 2; break 2;
} }
} }
}
} elseif (!is_array($value)) { } elseif (!is_array($value)) {
$newvaluelength = strlen(trim($value)); $newvaluelength = strlen(trim($value));
$newvaluelengthMB = mb_strlen(trim($value));
foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
$oldvaluelength = strlen(trim($existingvalue)); $oldvaluelength = strlen(trim($existingvalue));
$oldvaluelengthMB = mb_strlen(trim($existingvalue));
if (($newvaluelengthMB == $oldvaluelengthMB) && ($existingvalue == getid3_lib::iconv_fallback('UTF-8', 'ASCII', $value))) {
// https://github.com/JamesHeinrich/getID3/issues/338
// check for tags containing extended characters that may have been forced into limited-character storage (e.g. UTF8 values into ASCII)
// which will usually display unrepresentable characters as "?"
$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
break;
}
if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
break; break;
@ -1601,6 +1627,7 @@ class getid3_lib
} }
// attempt to standardize spelling of returned keys // attempt to standardize spelling of returned keys
if (!empty($ThisFileInfo['comments'])) {
$StandardizeFieldNames = array( $StandardizeFieldNames = array(
'tracknumber' => 'track_number', 'tracknumber' => 'track_number',
'track' => 'track_number', 'track' => 'track_number',
@ -1611,6 +1638,7 @@ class getid3_lib
unset($ThisFileInfo['comments'][$badkey]); unset($ThisFileInfo['comments'][$badkey]);
} }
} }
}
if ($option_tags_html) { if ($option_tags_html) {
// Copy ['comments'] to ['comments_html'] // Copy ['comments'] to ['comments_html']
@ -1734,6 +1762,7 @@ class getid3_lib
* @return float|bool * @return float|bool
*/ */
public static function getFileSizeSyscall($path) { public static function getFileSizeSyscall($path) {
$commandline = null;
$filesize = false; $filesize = false;
if (GETID3_OS_ISWINDOWS) { if (GETID3_OS_ISWINDOWS) {
@ -1795,7 +1824,7 @@ class getid3_lib
* *
* @return string * @return string
*/ */
public static function mb_basename($path, $suffix = null) { public static function mb_basename($path, $suffix = '') {
$splited = preg_split('#/#', rtrim($path, '/ ')); $splited = preg_split('#/#', rtrim($path, '/ '));
return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1); return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1);
} }

View File

@ -17,10 +17,6 @@ if (!defined('GETID3_OS_ISWINDOWS')) {
if (!defined('GETID3_INCLUDEPATH')) { if (!defined('GETID3_INCLUDEPATH')) {
define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
} }
// Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
define('IMG_JPG', IMAGETYPE_JPEG);
}
if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8)); define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8));
} }
@ -57,7 +53,7 @@ if ($open_basedir) {
if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
$basedir .= DIRECTORY_SEPARATOR; $basedir .= DIRECTORY_SEPARATOR;
} }
if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { if (strpos($temp_dir, $basedir) === 0) {
$found_valid_tempdir = true; $found_valid_tempdir = true;
break; break;
} }
@ -214,6 +210,140 @@ class getID3
*/ */
public $option_fread_buffer_size = 32768; public $option_fread_buffer_size = 32768;
// module-specific options
/** archive.rar
* if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3)
*
* @var bool
*/
public $options_archive_rar_use_php_rar_extension = true;
/** archive.gzip
* Optional file list - disable for speed.
* Decode gzipped files, if possible, and parse recursively (.tar.gz for example).
*
* @var bool
*/
public $options_archive_gzip_parse_contents = false;
/** audio.midi
* if false only parse most basic information, much faster for some files but may be inaccurate
*
* @var bool
*/
public $options_audio_midi_scanwholefile = true;
/** audio.mp3
* Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
* unrecommended, but may provide data from otherwise-unusable files.
*
* @var bool
*/
public $options_audio_mp3_allow_bruteforce = false;
/** audio.mp3
* number of frames to scan to determine if MPEG-audio sequence is valid
* Lower this number to 5-20 for faster scanning
* Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
*
* @var int
*/
public $options_audio_mp3_mp3_valid_check_frames = 50;
/** audio.wavpack
* Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK,
* significantly faster for very large files but other data may be missed
*
* @var bool
*/
public $options_audio_wavpack_quick_parsing = false;
/** audio-video.flv
* Break out of the loop if too many frames have been scanned; only scan this
* many if meta frame does not contain useful duration.
*
* @var int
*/
public $options_audiovideo_flv_max_frames = 100000;
/** audio-video.matroska
* If true, do not return information about CLUSTER chunks, since there's a lot of them
* and they're not usually useful [default: TRUE].
*
* @var bool
*/
public $options_audiovideo_matroska_hide_clusters = true;
/** audio-video.matroska
* True to parse the whole file, not only header [default: FALSE].
*
* @var bool
*/
public $options_audiovideo_matroska_parse_whole_file = false;
/** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
*
* @var bool
*/
public $options_audiovideo_quicktime_ReturnAtomData = false;
/** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
*
* @var bool
*/
public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false;
/** audio-video.swf
* return all parsed tags if true, otherwise do not return tags not parsed by getID3
*
* @var bool
*/
public $options_audiovideo_swf_ReturnAllTagData = false;
/** graphic.bmp
* return BMP palette
*
* @var bool
*/
public $options_graphic_bmp_ExtractPalette = false;
/** graphic.bmp
* return image data
*
* @var bool
*/
public $options_graphic_bmp_ExtractData = false;
/** graphic.png
* If data chunk is larger than this do not read it completely (getID3 only needs the first
* few dozen bytes for parsing).
*
* @var int
*/
public $options_graphic_png_max_data_bytes = 10000000;
/** misc.pdf
* return full details of PDF Cross-Reference Table (XREF)
*
* @var bool
*/
public $options_misc_pdf_returnXREF = false;
/** misc.torrent
* Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing.
* Override this value if you need to process files larger than 1MB
*
* @var int
*/
public $options_misc_torrent_max_torrent_filesize = 1048576;
// Public variables // Public variables
/** /**
@ -257,7 +387,7 @@ class getID3
*/ */
protected $startup_warning = ''; protected $startup_warning = '';
const VERSION = '1.9.20-202006061653'; const VERSION = '1.9.21-202109171300';
const FREAD_BUFFER_SIZE = 32768; const FREAD_BUFFER_SIZE = 32768;
const ATTACHMENTS_NONE = false; const ATTACHMENTS_NONE = false;
@ -637,6 +767,18 @@ class getID3
return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
} }
$class = new $class_name($this); $class = new $class_name($this);
// set module-specific options
foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) {
if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) {
list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches;
$GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here
if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) {
$class->$GOVsetting = $getid3_object_vars_value;
}
}
}
$class->Analyze(); $class->Analyze();
unset($class); unset($class);
@ -1355,6 +1497,16 @@ class getID3
'fail_ape' => 'ERROR', 'fail_ape' => 'ERROR',
), ),
// TORRENT - .torrent
'torrent' => array(
'pattern' => '^(d8\\:announce|d7\\:comment)',
'group' => 'misc',
'module' => 'torrent',
'mime_type' => 'application/x-bittorrent',
'fail_id3' => 'ERROR',
'fail_ape' => 'ERROR',
),
// CUE - data - CUEsheet (index to single-file disc images) // CUE - data - CUEsheet (index to single-file disc images)
'cue' => array( 'cue' => array(
'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
@ -1489,7 +1641,7 @@ class getID3
if (is_string($value)) { if (is_string($value)) {
$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
} }
if ($value) { if (isset($value) && $value !== "") {
if (!is_numeric($key)) { if (!is_numeric($key)) {
$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
} else { } else {
@ -2096,8 +2248,9 @@ abstract class getid3_handler
$this->data_string_position = $this->data_string_length + $bytes; $this->data_string_position = $this->data_string_length + $bytes;
break; break;
} }
return 0; return 0; // fseek returns 0 on success
} else { }
$pos = $bytes; $pos = $bytes;
if ($whence == SEEK_CUR) { if ($whence == SEEK_CUR) {
$pos = $this->ftell() + $bytes; $pos = $this->ftell() + $bytes;
@ -2107,8 +2260,13 @@ abstract class getid3_handler
if (!getid3_lib::intValueSupported($pos)) { if (!getid3_lib::intValueSupported($pos)) {
throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
} }
// https://github.com/JamesHeinrich/getID3/issues/327
$result = fseek($this->getid3->fp, $bytes, $whence);
if ($result !== 0) { // fseek returns 0 on success
throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10);
} }
return fseek($this->getid3->fp, $bytes, $whence); return $result;
} }
/** /**
@ -2224,6 +2382,8 @@ abstract class getid3_handler
* @throws getid3_exception * @throws getid3_exception
*/ */
public function saveAttachment($name, $offset, $length, $image_mime=null) { public function saveAttachment($name, $offset, $length, $image_mime=null) {
$fp_dest = null;
$dest = null;
try { try {
// do not extract at all // do not extract at all

View File

@ -93,6 +93,7 @@ class getid3_asf extends getid3_handler
$offset = 0; $offset = 0;
$thisfile_asf_streambitratepropertiesobject = array(); $thisfile_asf_streambitratepropertiesobject = array();
$thisfile_asf_codeclistobject = array(); $thisfile_asf_codeclistobject = array();
$StreamPropertiesObjectData = array();
for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) {
$NextObjectGUID = substr($ASFHeaderData, $offset, 16); $NextObjectGUID = substr($ASFHeaderData, $offset, 16);
@ -283,7 +284,7 @@ class getid3_asf extends getid3_handler
$thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
$offset += 2; $offset += 2;
if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) {
$this->warning('header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'); $this->warning('header_extension_object.reserved_2 ('.$thisfile_asf_headerextensionobject['reserved_2'].') does not match expected value of "6"');
//return false; //return false;
break; break;
} }
@ -535,7 +536,7 @@ class getid3_asf extends getid3_handler
$thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
$offset += 2; $offset += 2;
if ($thisfile_asf_markerobject['reserved_2'] != 0) { if ($thisfile_asf_markerobject['reserved_2'] != 0) {
$this->warning('marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'); $this->warning('marker_object.reserved_2 ('.$thisfile_asf_markerobject['reserved_2'].') does not match expected value of "0"');
break; break;
} }
$thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
@ -1193,7 +1194,7 @@ class getid3_asf extends getid3_handler
$thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2));
$offset += 2; $offset += 2;
if ($thisfile_asf_dataobject['reserved'] != 0x0101) { if ($thisfile_asf_dataobject['reserved'] != 0x0101) {
$this->warning('data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'); $this->warning('data_object.reserved (0x'.sprintf('%04X', $thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"');
//return false; //return false;
break; break;
} }

View File

@ -161,6 +161,7 @@ class getid3_flv extends getid3_handler
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07; $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
$FLVvideoHeader = $this->fread(11); $FLVvideoHeader = $this->fread(11);
$PictureSizeEnc = array();
if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) { if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
// this code block contributed by: moysevichØgmail*com // this code block contributed by: moysevichØgmail*com

View File

@ -224,14 +224,14 @@ class getid3_matroska extends getid3_handler
* *
* @var bool * @var bool
*/ */
public static $hide_clusters = true; public $hide_clusters = true;
/** /**
* True to parse the whole file, not only header [default: FALSE]. * True to parse the whole file, not only header [default: FALSE].
* *
* @var bool * @var bool
*/ */
public static $parse_whole_file = false; public $parse_whole_file = false;
/* /*
* Private parser settings/placeholders. * Private parser settings/placeholders.
@ -586,7 +586,7 @@ class getid3_matroska extends getid3_handler
$info['matroska']['segment'][0]['length'] = $top_element['length']; $info['matroska']['segment'][0]['length'] = $top_element['length'];
while ($this->getEBMLelement($element_data, $top_element['end'])) { while ($this->getEBMLelement($element_data, $top_element['end'])) {
if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required if ($element_data['id'] != EBML_ID_CLUSTER || !$this->hide_clusters) { // collect clusters only if required
$info['matroska']['segments'][] = $element_data; $info['matroska']['segments'][] = $element_data;
} }
switch ($element_data['id']) { switch ($element_data['id']) {
@ -618,7 +618,7 @@ class getid3_matroska extends getid3_handler
$this->warning('seek_entry[target_id] unexpectedly not set at '.$seek_entry['offset']); $this->warning('seek_entry[target_id] unexpectedly not set at '.$seek_entry['offset']);
break; break;
} }
if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !self::$hide_clusters) { // collect clusters only if required if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !$this->hide_clusters) { // collect clusters only if required
$info['matroska']['seek'][] = $seek_entry; $info['matroska']['seek'][] = $seek_entry;
} }
break; break;
@ -905,7 +905,7 @@ class getid3_matroska extends getid3_handler
break; break;
case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams.
if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway if ($this->hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway
$this->current_offset = $element_data['end']; $this->current_offset = $element_data['end'];
break; break;
} }
@ -1246,12 +1246,12 @@ class getid3_matroska extends getid3_handler
} }
$this->current_offset = $subelement['end']; $this->current_offset = $subelement['end'];
} }
if (!self::$hide_clusters) { if (!$this->hide_clusters) {
$info['matroska']['cluster'][] = $cluster_entry; $info['matroska']['cluster'][] = $cluster_entry;
} }
// check to see if all the data we need exists already, if so, break out of the loop // check to see if all the data we need exists already, if so, break out of the loop
if (!self::$parse_whole_file) { if (!$this->parse_whole_file) {
if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) {

View File

@ -24,7 +24,18 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE_
class getid3_quicktime extends getid3_handler class getid3_quicktime extends getid3_handler
{ {
public $ReturnAtomData = true; /** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
*
* @var bool
*/
public $ReturnAtomData = false;
/** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
*
* @var bool
*/
public $ParseAllPossibleAtoms = false; public $ParseAllPossibleAtoms = false;
/** /**
@ -170,7 +181,7 @@ class getid3_quicktime extends getid3_handler
} }
} }
if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { if (!isset($info['bitrate']) && !empty($info['playtime_seconds'])) {
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
} }
if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
@ -560,15 +571,28 @@ class getid3_quicktime extends getid3_handler
default: default:
$atom_structure['data'] = substr($boxdata, 8); $atom_structure['data'] = substr($boxdata, 8);
if ($atomname == 'covr') { if ($atomname == 'covr') {
// not a foolproof check, but better than nothing if (!empty($atom_structure['data'])) {
if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/unknown'; // provide default MIME type to ensure array keys exist
$atom_structure['image_mime'] = 'image/jpeg'; if (function_exists('getimagesizefromstring') && ($getimagesize = getimagesizefromstring($atom_structure['data'])) && !empty($getimagesize['mime'])) {
} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { $atom_structure['image_mime'] = $getimagesize['mime'];
$atom_structure['image_mime'] = 'image/png'; } else {
} elseif (preg_match('#^GIF#', $atom_structure['data'])) { // if getimagesizefromstring is not available, or fails for some reason, fall back to simple detection of common image formats
$atom_structure['image_mime'] = 'image/gif'; $ImageFormatSignatures = array(
'image/jpeg' => "\xFF\xD8\xFF",
'image/png' => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",
'image/gif' => 'GIF',
);
foreach ($ImageFormatSignatures as $mime => $image_format_signature) {
if (substr($atom_structure['data'], 0, strlen($image_format_signature)) == $image_format_signature) {
$atom_structure['image_mime'] = $mime;
break;
}
}
} }
$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover'); $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
} else {
$this->warning('Unknown empty "covr" image at offset '.$baseoffset);
}
} }
break; break;
@ -728,11 +752,13 @@ class getid3_quicktime extends getid3_handler
$atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
$atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag'];
$ptv_lookup[0] = 'normal'; $ptv_lookup = array(
$ptv_lookup[1] = 'double'; 0 => 'normal',
$ptv_lookup[2] = 'half'; 1 => 'double',
$ptv_lookup[3] = 'full'; 2 => 'half',
$ptv_lookup[4] = 'current'; 3 => 'full',
4 => 'current'
);
if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
} else { } else {
@ -908,13 +934,13 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
$atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2));
$atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2));
$atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (((int) $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
$atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);
if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
$info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
$info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
$info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); $info['quicktime']['video']['codec'] = (((int) $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
$info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
$info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];
@ -1598,33 +1624,61 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
break; break;
case 'NCDT': case 'NCDT':
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // https://exiftool.org/TagNames/Nikon.html
// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
break; break;
case 'NCTH': // Nikon Camera THumbnail image case 'NCTH': // Nikon Camera THumbnail image
case 'NCVW': // Nikon Camera preVieW image case 'NCVW': // Nikon Camera preVieW image
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'NCM1': // Nikon Camera preview iMage 1
case 'NCM2': // Nikon Camera preview iMage 2
// https://exiftool.org/TagNames/Nikon.html
if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
$descriptions = array(
'NCTH' => 'Nikon Camera Thumbnail Image',
'NCVW' => 'Nikon Camera Preview Image',
'NCM1' => 'Nikon Camera Preview Image 1',
'NCM2' => 'Nikon Camera Preview Image 2',
);
$atom_structure['data'] = $atom_data; $atom_structure['data'] = $atom_data;
$atom_structure['image_mime'] = 'image/jpeg'; $atom_structure['image_mime'] = 'image/jpeg';
$atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); $atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); $info['quicktime']['comments']['picture'][] = array(
'image_mime' => $atom_structure['image_mime'],
'data' => $atom_data,
'description' => $atom_structure['description']
);
} }
break; break;
case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
$atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true);
$nikonNCTG = new getid3_tag_nikon_nctg($this->getid3);
$atom_structure['data'] = $nikonNCTG->parse($atom_data);
break; break;
case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'NCHD': // Nikon:MakerNoteVersion - https://exiftool.org/TagNames/Nikon.html
case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html $makerNoteVersion = '';
case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
if (ord($atom_data[$i]) >= 0x00 && ord($atom_data[$i]) <= 0x1F) {
$makerNoteVersion .= ' '.ord($atom_data[$i]);
} else {
$makerNoteVersion .= $atom_data[$i];
}
}
$makerNoteVersion = rtrim($makerNoteVersion, "\x00");
$atom_structure['data'] = array(
'MakerNoteVersion' => $makerNoteVersion
);
break;
case 'NCDB': // Nikon - https://exiftool.org/TagNames/Nikon.html
case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
$atom_structure['data'] = $atom_data; $atom_structure['data'] = $atom_data;
break; break;
case "\x00\x00\x00\x00": case "\x00\x00\x00\x00":
// some kind of metacontainer, may contain a big data dump such as: // some kind of metacontainer, may contain a big data dump such as:
// 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 // 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 // https://xhelmboyx.tripod.com/formats/qti-layout.txt
$atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
@ -1721,6 +1775,7 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
'unknown_data' => array(), 'unknown_data' => array(),
'debug_list' => '', // Used to debug variables stored as comma delimited strings 'debug_list' => '', // Used to debug variables stored as comma delimited strings
); );
$debug_structure = array();
$debug_structure['debug_items'] = array(); $debug_structure['debug_items'] = array();
// Can start loop here to decode all sensor data in 32 Byte chunks: // Can start loop here to decode all sensor data in 32 Byte chunks:
foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) { foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) {
@ -2039,7 +2094,7 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
* @return array|false * @return array|false
*/ */
public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
$atom_structure = false; $atom_structure = array();
$subatomoffset = 0; $subatomoffset = 0;
$subatomcounter = 0; $subatomcounter = 0;
if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
@ -2057,17 +2112,22 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
$subatomoffset += 4; $subatomoffset += 4;
continue; continue;
} }
return $atom_structure; break;
} }
if (strlen($subatomdata) < ($subatomsize - 8)) { if (strlen($subatomdata) < ($subatomsize - 8)) {
// we don't have enough data to decode the subatom. // we don't have enough data to decode the subatom.
// this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large // this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large
// so we passed in the start of a following atom incorrectly? // so we passed in the start of a following atom incorrectly?
return $atom_structure; break;
} }
$atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); $atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
$subatomoffset += $subatomsize; $subatomoffset += $subatomsize;
} }
if (empty($atom_structure)) {
return false;
}
return $atom_structure; return $atom_structure;
} }
@ -2552,8 +2612,9 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
static $QuicktimeContentRatingLookup = array(); static $QuicktimeContentRatingLookup = array();
if (empty($QuicktimeContentRatingLookup)) { if (empty($QuicktimeContentRatingLookup)) {
$QuicktimeContentRatingLookup[0] = 'None'; $QuicktimeContentRatingLookup[0] = 'None';
$QuicktimeContentRatingLookup[1] = 'Explicit';
$QuicktimeContentRatingLookup[2] = 'Clean'; $QuicktimeContentRatingLookup[2] = 'Clean';
$QuicktimeContentRatingLookup[4] = 'Explicit'; $QuicktimeContentRatingLookup[4] = 'Explicit (old)';
} }
return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
} }
@ -2606,189 +2667,6 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
} }
/**
* @param string $atom_data
*
* @return array
*/
public function QuicktimeParseNikonNCTG($atom_data) {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
// Data is stored as records of:
// * 4 bytes record type
// * 2 bytes size of data field type:
// 0x0001 = flag (size field *= 1-byte)
// 0x0002 = char (size field *= 1-byte)
// 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
// 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
// 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
// 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
// 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
// * 2 bytes data size field
// * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
// all integers are stored BigEndian
$NCTGtagName = array(
0x00000001 => 'Make',
0x00000002 => 'Model',
0x00000003 => 'Software',
0x00000011 => 'CreateDate',
0x00000012 => 'DateTimeOriginal',
0x00000013 => 'FrameCount',
0x00000016 => 'FrameRate',
0x00000022 => 'FrameWidth',
0x00000023 => 'FrameHeight',
0x00000032 => 'AudioChannels',
0x00000033 => 'AudioBitsPerSample',
0x00000034 => 'AudioSampleRate',
0x02000001 => 'MakerNoteVersion',
0x02000005 => 'WhiteBalance',
0x0200000b => 'WhiteBalanceFineTune',
0x0200001e => 'ColorSpace',
0x02000023 => 'PictureControlData',
0x02000024 => 'WorldTime',
0x02000032 => 'UnknownInfo',
0x02000083 => 'LensType',
0x02000084 => 'Lens',
);
$offset = 0;
$data = null;
$datalength = strlen($atom_data);
$parsed = array();
while ($offset < $datalength) {
$record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4;
$data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
$data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
switch ($data_size_type) {
case 0x0001: // 0x0001 = flag (size field *= 1-byte)
$data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
$offset += ($data_size * 1);
break;
case 0x0002: // 0x0002 = char (size field *= 1-byte)
$data = substr($atom_data, $offset, $data_size * 1);
$offset += ($data_size * 1);
$data = rtrim($data, "\x00");
break;
case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
$data = '';
for ($i = $data_size - 1; $i >= 0; $i--) {
$data .= substr($atom_data, $offset + ($i * 2), 2);
}
$data = getid3_lib::BigEndian2Int($data);
$offset += ($data_size * 2);
break;
case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
$data = '';
for ($i = $data_size - 1; $i >= 0; $i--) {
$data .= substr($atom_data, $offset + ($i * 4), 4);
}
$data = getid3_lib::BigEndian2Int($data);
$offset += ($data_size * 4);
break;
case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
$data = array();
for ($i = 0; $i < $data_size; $i++) {
$numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
$denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
if ($denomninator == 0) {
$data[$i] = false;
} else {
$data[$i] = (double) $numerator / $denomninator;
}
}
$offset += (8 * $data_size);
if (count($data) == 1) {
$data = $data[0];
}
break;
case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
$data = substr($atom_data, $offset, $data_size * 1);
$offset += ($data_size * 1);
break;
case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
$data = substr($atom_data, $offset, $data_size * 2);
$offset += ($data_size * 2);
break;
default:
echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';
break 2;
}
switch ($record_type) {
case 0x00000011: // CreateDate
case 0x00000012: // DateTimeOriginal
$data = strtotime($data);
break;
case 0x0200001e: // ColorSpace
switch ($data) {
case 1:
$data = 'sRGB';
break;
case 2:
$data = 'Adobe RGB';
break;
}
break;
case 0x02000023: // PictureControlData
$PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
$FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a');
$ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a');
$data = array(
'PictureControlVersion' => substr($data, 0, 4),
'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
//'?' => substr($data, 44, 4),
'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))],
'PictureControlQuickAdjust' => ord(substr($data, 49, 1)),
'Sharpness' => ord(substr($data, 50, 1)),
'Contrast' => ord(substr($data, 51, 1)),
'Brightness' => ord(substr($data, 52, 1)),
'Saturation' => ord(substr($data, 53, 1)),
'HueAdjustment' => ord(substr($data, 54, 1)),
'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))],
'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))],
'ToningSaturation' => ord(substr($data, 57, 1)),
);
break;
case 0x02000024: // WorldTime
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime
// timezone is stored as offset from GMT in minutes
$timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
if ($timezone & 0x8000) {
$timezone = 0 - (0x10000 - $timezone);
}
$timezone /= 60;
$dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1));
switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
case 2:
$datedisplayformat = 'D/M/Y'; break;
case 1:
$datedisplayformat = 'M/D/Y'; break;
case 0:
default:
$datedisplayformat = 'Y/M/D'; break;
}
$data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
break;
case 0x02000083: // LensType
$data = array(
//'_' => $data,
'mf' => (bool) ($data & 0x01),
'd' => (bool) ($data & 0x02),
'g' => (bool) ($data & 0x04),
'vr' => (bool) ($data & 0x08),
);
break;
}
$tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
$parsed[$tag_name] = $data;
}
return $parsed;
}
/** /**
* @param string $keyname * @param string $keyname
* @param string|array $data * @param string|array $data

View File

@ -56,6 +56,7 @@ class getid3_riff extends getid3_handler
$thisfile_riff_video = &$thisfile_riff['video']; $thisfile_riff_video = &$thisfile_riff['video'];
$thisfile_riff_WAVE = array(); $thisfile_riff_WAVE = array();
$Original = array();
$Original['avdataoffset'] = $info['avdataoffset']; $Original['avdataoffset'] = $info['avdataoffset'];
$Original['avdataend'] = $info['avdataend']; $Original['avdataend'] = $info['avdataend'];
@ -296,9 +297,18 @@ class getid3_riff extends getid3_handler
// shortcut // shortcut
$thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0]; $thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0];
$thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); $thisfile_riff_WAVE_bext_0['title'] = substr($thisfile_riff_WAVE_bext_0['data'], 0, 256);
$thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); $thisfile_riff_WAVE_bext_0['author'] = substr($thisfile_riff_WAVE_bext_0['data'], 256, 32);
$thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); $thisfile_riff_WAVE_bext_0['reference'] = substr($thisfile_riff_WAVE_bext_0['data'], 288, 32);
foreach (array('title','author','reference') as $bext_key) {
// Some software (notably Logic Pro) may not blank existing data before writing a null-terminated string to the offsets
// assigned for text fields, resulting in a null-terminated string (or possibly just a single null) followed by garbage
// Keep only string as far as first null byte, discard rest of fixed-width data
// https://github.com/JamesHeinrich/getID3/issues/263
$null_terminator_offset = strpos($thisfile_riff_WAVE_bext_0[$bext_key], "\x00");
$thisfile_riff_WAVE_bext_0[$bext_key] = substr($thisfile_riff_WAVE_bext_0[$bext_key], 0, $null_terminator_offset);
}
$thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10);
$thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8);
$thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8));
@ -307,6 +317,7 @@ class getid3_riff extends getid3_handler
$thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601)));
if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) {
if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) {
$bext_timestamp = array();
list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date;
list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time;
$thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']);
@ -451,7 +462,62 @@ class getid3_riff extends getid3_handler
} }
} }
if (isset($thisfile_riff_WAVE['guan'][0]['data'])) {
// shortcut
$thisfile_riff_WAVE_guan_0 = &$thisfile_riff_WAVE['guan'][0];
if (!empty($thisfile_riff_WAVE_guan_0['data']) && (substr($thisfile_riff_WAVE_guan_0['data'], 0, 14) == 'GUANO|Version:')) {
$thisfile_riff['guano'] = array();
foreach (explode("\n", $thisfile_riff_WAVE_guan_0['data']) as $line) {
if ($line) {
@list($key, $value) = explode(':', $line, 2);
if (substr($value, 0, 3) == '[{"') {
if ($decoded = @json_decode($value, true)) {
if (!empty($decoded) && (count($decoded) == 1)) {
$value = $decoded[0];
} else {
$value = $decoded;
}
}
}
$thisfile_riff['guano'] = array_merge_recursive($thisfile_riff['guano'], getid3_lib::CreateDeepArray($key, '|', $value));
}
}
// https://www.wildlifeacoustics.com/SCHEMA/GUANO.html
foreach ($thisfile_riff['guano'] as $key => $value) {
switch ($key) {
case 'Loc Position':
if (preg_match('#^([\\+\\-]?[0-9]+\\.[0-9]+) ([\\+\\-]?[0-9]+\\.[0-9]+)$#', $value, $matches)) {
list($dummy, $latitude, $longitude) = $matches;
$thisfile_riff['comments']['gps_latitude'][0] = floatval($latitude);
$thisfile_riff['comments']['gps_longitude'][0] = floatval($longitude);
$thisfile_riff['guano'][$key] = floatval($latitude).' '.floatval($longitude);
}
break;
case 'Loc Elevation': // Elevation/altitude above mean sea level in meters
$thisfile_riff['comments']['gps_altitude'][0] = floatval($value);
$thisfile_riff['guano'][$key] = (float) $value;
break;
case 'Filter HP': // High-pass filter frequency in kHz
case 'Filter LP': // Low-pass filter frequency in kHz
case 'Humidity': // Relative humidity as a percentage
case 'Length': // Recording length in seconds
case 'Loc Accuracy': // Estimated Position Error in meters
case 'Temperature Ext': // External temperature in degrees Celsius outside the recorder's housing
case 'Temperature Int': // Internal temperature in degrees Celsius inside the recorder's housing
$thisfile_riff['guano'][$key] = (float) $value;
break;
case 'Samplerate': // Recording sample rate, Hz
case 'TE': // Time-expansion factor. If not specified, then 1 (no time-expansion a.k.a. direct-recording) is assumed.
$thisfile_riff['guano'][$key] = (int) $value;
break;
}
}
} else {
$this->warning('RIFF.guan data not in expected format');
}
}
if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) {
$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
@ -733,6 +799,7 @@ class getid3_riff extends getid3_handler
} }
if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) {
if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) {
$thisfile_riff_raw_strf_strhfccType_streamindex = null;
for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) {
if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) {
$strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'];
@ -1069,7 +1136,7 @@ class getid3_riff extends getid3_handler
if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) {
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2 = new getid3_id3v2($getid3_temp);
$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8;
if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
@ -1172,7 +1239,7 @@ class getid3_riff extends getid3_handler
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true);
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_mpeg = new getid3_mpeg($getid3_temp); $getid3_mpeg = new getid3_mpeg($getid3_temp);
$getid3_mpeg->Analyze(); $getid3_mpeg->Analyze();
if (empty($getid3_temp->info['error'])) { if (empty($getid3_temp->info['error'])) {
@ -1258,7 +1325,7 @@ class getid3_riff extends getid3_handler
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2 = new getid3_id3v2($getid3_temp);
$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8; $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8;
if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) { if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
@ -1514,6 +1581,9 @@ class getid3_riff extends getid3_handler
$RIFFchunk = false; $RIFFchunk = false;
$FoundAllChunksWeNeed = false; $FoundAllChunksWeNeed = false;
$LISTchunkParent = null;
$LISTchunkMaxOffset = null;
$AC3syncwordBytes = pack('n', getid3_ac3::syncword); // 0x0B77 -> "\x0B\x77"
try { try {
$this->fseek($startoffset); $this->fseek($startoffset);
@ -1557,7 +1627,7 @@ class getid3_riff extends getid3_handler
// MP3 // MP3
if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) {
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
$getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize;
$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
@ -1575,11 +1645,10 @@ class getid3_riff extends getid3_handler
unset($getid3_temp, $getid3_mp3); unset($getid3_temp, $getid3_mp3);
} }
} elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { } elseif (strpos($FirstFourBytes, $AC3syncwordBytes) === 0) {
// AC3 // AC3
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
$getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize;
$getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3 = new getid3_ac3($getid3_temp);
@ -1640,7 +1709,7 @@ class getid3_riff extends getid3_handler
// Probably is MP3 data // Probably is MP3 data
if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) {
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
$getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_temp->info['avdataend'] = $info['avdataend'];
$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
@ -1652,12 +1721,12 @@ class getid3_riff extends getid3_handler
unset($getid3_temp, $getid3_mp3); unset($getid3_temp, $getid3_mp3);
} }
} elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { } elseif (($isRegularAC3 = (substr($testData, 0, 2) == $AC3syncwordBytes)) || substr($testData, 8, 2) == strrev($AC3syncwordBytes)) {
// This is probably AC-3 data // This is probably AC-3 data
$getid3_temp = new getID3(); $getid3_temp = new getID3();
if ($isRegularAC3) { if ($isRegularAC3) {
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
$getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_temp->info['avdataend'] = $info['avdataend'];
} }
@ -1673,6 +1742,8 @@ class getid3_riff extends getid3_handler
$ac3_data .= substr($testData, 8 + $i + 1, 1); $ac3_data .= substr($testData, 8 + $i + 1, 1);
$ac3_data .= substr($testData, 8 + $i + 0, 1); $ac3_data .= substr($testData, 8 + $i + 0, 1);
} }
$getid3_ac3->getid3->info['avdataoffset'] = 0;
$getid3_ac3->getid3->info['avdataend'] = strlen($ac3_data);
$getid3_ac3->AnalyzeString($ac3_data); $getid3_ac3->AnalyzeString($ac3_data);
} }
@ -1691,7 +1762,7 @@ class getid3_riff extends getid3_handler
// This is probably DTS data // This is probably DTS data
$getid3_temp = new getID3(); $getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
$getid3_dts = new getid3_dts($getid3_temp); $getid3_dts = new getid3_dts($getid3_temp);
$getid3_dts->Analyze(); $getid3_dts->Analyze();
@ -1732,6 +1803,8 @@ class getid3_riff extends getid3_handler
case 'indx': case 'indx':
case 'MEXT': case 'MEXT':
case 'DISP': case 'DISP':
case 'wamd':
case 'guan':
// always read data in // always read data in
case 'JUNK': case 'JUNK':
// should be: never read data in // should be: never read data in
@ -2076,6 +2149,7 @@ class getid3_riff extends getid3_handler
*/ */
public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {
$parsed = array();
$parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure
$parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels $parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels
$parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner $parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner

View File

@ -402,6 +402,7 @@ class getid3_flac extends getid3_handler
public function parsePICTURE() { public function parsePICTURE() {
$info = &$this->getid3->info; $info = &$this->getid3->info;
$picture = array();
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); $picture['picturetype'] = self::pictureTypeLookup($picture['typeid']);
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4))); $picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));

View File

@ -18,12 +18,6 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
exit; exit;
} }
// number of frames to scan to determine if MPEG-audio sequence is valid
// Lower this number to 5-20 for faster scanning
// Increase this number to 50+ for most accurate detection of valid VBR/CBR
// mpeg-audio streams
define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
class getid3_mp3 extends getid3_handler class getid3_mp3 extends getid3_handler
{ {
@ -35,6 +29,15 @@ class getid3_mp3 extends getid3_handler
*/ */
public $allow_bruteforce = false; public $allow_bruteforce = false;
/**
* number of frames to scan to determine if MPEG-audio sequence is valid
* Lower this number to 5-20 for faster scanning
* Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
*
* @var int
*/
public $mp3_valid_check_frames = 50;
/** /**
* @return bool * @return bool
*/ */
@ -55,6 +58,7 @@ class getid3_mp3 extends getid3_handler
$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
} }
$CurrentDataLAMEversionString = null;
if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
$synchoffsetwarning = 'Unknown data before synch '; $synchoffsetwarning = 'Unknown data before synch ';
@ -121,6 +125,12 @@ class getid3_mp3 extends getid3_handler
if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
$PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
if (!empty($info['audio']['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version']) && ($info['audio']['encoder'] == $info['mpeg']['audio']['LAME']['short_version'])) {
if (preg_match('#^LAME[0-9\\.]+#', $PossiblyLongerLAMEversion_NewString, $matches)) {
// "LAME3.100" -> "LAME3.100.1", but avoid including "(alpha)" and similar
$info['mpeg']['audio']['LAME']['short_version'] = $matches[0];
}
}
$info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
} }
} }
@ -295,7 +305,7 @@ class getid3_mp3 extends getid3_handler
} elseif (!empty($info['audio']['bitrate'])) { } elseif (!empty($info['audio']['bitrate'])) {
if ($info['audio']['bitrate_mode'] == 'cbr') { if ($info['audio']['bitrate_mode'] == 'cbr') {
$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); $encoder_options = strtoupper($info['audio']['bitrate_mode']).round($info['audio']['bitrate'] / 1000);
} else { } else {
$encoder_options = strtoupper($info['audio']['bitrate_mode']); $encoder_options = strtoupper($info['audio']['bitrate_mode']);
} }
@ -488,7 +498,7 @@ class getid3_mp3 extends getid3_handler
if ($MPEGaudioHeaderValidCache[$head4_key]) { if ($MPEGaudioHeaderValidCache[$head4_key]) {
$thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
} else { } else {
$this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset); $this->warning('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
return false; return false;
} }
@ -722,15 +732,17 @@ class getid3_mp3 extends getid3_handler
$thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
$thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9); $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
if (preg_match('#^LAME([0-9\\.a-z]+)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) { //$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
$thisfile_mpeg_audio_lame['numeric_version'] = '';
if (preg_match('#^LAME([0-9\\.a-z]*)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
$thisfile_mpeg_audio_lame['short_version'] = $matches[0]; $thisfile_mpeg_audio_lame['short_version'] = $matches[0];
$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1]; $thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
} }
if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) {
foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) { foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number); $thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
} }
//if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') { //if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207 if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
@ -915,6 +927,7 @@ class getid3_mp3 extends getid3_handler
} }
} }
}
} else { } else {
@ -1009,6 +1022,22 @@ class getid3_mp3 extends getid3_handler
if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
return false; return false;
} }
if (!empty($this->getid3->info['mp3_validity_check_bitrates']) && !empty($thisfile_mpeg_audio['bitrate_mode']) && ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') && !empty($thisfile_mpeg_audio['VBR_bitrate'])) {
// https://github.com/JamesHeinrich/getID3/issues/287
if (count(array_keys($this->getid3->info['mp3_validity_check_bitrates'])) == 1) {
list($cbr_bitrate_in_short_scan) = array_keys($this->getid3->info['mp3_validity_check_bitrates']);
$deviation_cbr_from_header_bitrate = abs($thisfile_mpeg_audio['VBR_bitrate'] - $cbr_bitrate_in_short_scan) / $cbr_bitrate_in_short_scan;
if ($deviation_cbr_from_header_bitrate < 0.01) {
// VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself?
// If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR
$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
//$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames');
}
}
}
if (isset($this->getid3->info['mp3_validity_check_bitrates'])) {
unset($this->getid3->info['mp3_validity_check_bitrates']);
}
} }
@ -1130,8 +1159,9 @@ class getid3_mp3 extends getid3_handler
$firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']); $firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
$this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { $info['mp3_validity_check_bitrates'] = array();
// check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch for ($i = 0; $i < $this->mp3_valid_check_frames; $i++) {
// check next (default: 50) frames for validity, to make sure we haven't run across a false synch
if (($nextframetestoffset + 4) >= $info['avdataend']) { if (($nextframetestoffset + 4) >= $info['avdataend']) {
// end of file // end of file
return true; return true;
@ -1139,6 +1169,7 @@ class getid3_mp3 extends getid3_handler
$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); $nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
getid3_lib::safe_inc($info['mp3_validity_check_bitrates'][$nextframetestarray['mpeg']['audio']['bitrate']]);
if ($ScanAsCBR) { if ($ScanAsCBR) {
// force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
@ -1273,6 +1304,7 @@ class getid3_mp3 extends getid3_handler
$LongMPEGbitrateLookup = array(); $LongMPEGbitrateLookup = array();
$LongMPEGpaddingLookup = array(); $LongMPEGpaddingLookup = array();
$LongMPEGfrequencyLookup = array(); $LongMPEGfrequencyLookup = array();
$Distribution = array();
$Distribution['bitrate'] = array(); $Distribution['bitrate'] = array();
$Distribution['frequency'] = array(); $Distribution['frequency'] = array();
$Distribution['layer'] = array(); $Distribution['layer'] = array();
@ -1433,6 +1465,9 @@ class getid3_mp3 extends getid3_handler
$header = $this->fread($sync_seek_buffer_size); $header = $this->fread($sync_seek_buffer_size);
$sync_seek_buffer_size = strlen($header); $sync_seek_buffer_size = strlen($header);
$SynchSeekOffset = 0; $SynchSeekOffset = 0;
$SyncSeekAttempts = 0;
$SyncSeekAttemptsMax = 1000;
$FirstFrameThisfileInfo = null;
while ($SynchSeekOffset < $sync_seek_buffer_size) { while ($SynchSeekOffset < $sync_seek_buffer_size) {
if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) { if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
@ -1471,7 +1506,24 @@ class getid3_mp3 extends getid3_handler
return false; return false;
} }
if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // synch detected if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // possible synch detected
if (++$SyncSeekAttempts >= $SyncSeekAttemptsMax) {
// https://github.com/JamesHeinrich/getID3/issues/286
// corrupt files claiming to be MP3, with a large number of 0xFF bytes near the beginning, can cause this loop to take a very long time
// should have escape condition to avoid spending too much time scanning a corrupt file
// if a synch's not found within the first 128k bytes, then give up
$this->error('Could not find valid MPEG audio synch after scanning '.$SyncSeekAttempts.' candidate offsets');
if (isset($info['audio']['bitrate'])) {
unset($info['audio']['bitrate']);
}
if (isset($info['mpeg']['audio'])) {
unset($info['mpeg']['audio']);
}
if (empty($info['mpeg'])) {
unset($info['mpeg']);
}
return false;
}
$FirstFrameAVDataOffset = null; $FirstFrameAVDataOffset = null;
if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
$FirstFrameThisfileInfo = $info; $FirstFrameThisfileInfo = $info;
@ -1511,9 +1563,9 @@ class getid3_mp3 extends getid3_handler
if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
$info = $dummy; $info = $dummy;
$info['avdataoffset'] = $GarbageOffsetEnd; $info['avdataoffset'] = $GarbageOffsetEnd;
$this->warning('apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd); $this->warning('apparently-valid VBR header not used because could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd);
} else { } else {
$this->warning('using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'); $this->warning('using data from VBR header even though could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
} }
} }
} }
@ -1558,6 +1610,7 @@ class getid3_mp3 extends getid3_handler
$pct_data_scanned = 0; $pct_data_scanned = 0;
for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
$frames_scanned_this_segment = 0; $frames_scanned_this_segment = 0;
$scan_start_offset = array();
if ($this->ftell() >= $info['avdataend']) { if ($this->ftell() >= $info['avdataend']) {
break; break;
} }
@ -1887,6 +1940,7 @@ class getid3_mp3 extends getid3_handler
return false; return false;
} }
$MPEGrawHeader = array();
$MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
$MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB $MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB
$MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC $MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC

View File

@ -529,6 +529,7 @@ class getid3_ogg extends getid3_handler
*/ */
public function ParseOggPageHeader() { public function ParseOggPageHeader() {
// http://xiph.org/ogg/vorbis/doc/framing.html // http://xiph.org/ogg/vorbis/doc/framing.html
$oggheader = array();
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
$filedata = $this->fread($this->getid3->fread_buffer_size()); $filedata = $this->fread($this->getid3->fread_buffer_size());
@ -680,7 +681,7 @@ class getid3_ogg extends getid3_handler
$VorbisCommentPage++; $VorbisCommentPage++;
$oggpageinfo = $this->ParseOggPageHeader(); if ($oggpageinfo = $this->ParseOggPageHeader()) {
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
// First, save what we haven't read yet // First, save what we haven't read yet
@ -709,6 +710,10 @@ class getid3_ogg extends getid3_handler
$commentdata .= $this->fread($readlength); $commentdata .= $this->fread($readlength);
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
} else {
$this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell());
break;
}
} }
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);

View File

@ -360,6 +360,7 @@ class getid3_apetag extends getid3_handler
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
// shortcut // shortcut
$headerfooterinfo = array();
$headerfooterinfo['raw'] = array(); $headerfooterinfo['raw'] = array();
$headerfooterinfo_raw = &$headerfooterinfo['raw']; $headerfooterinfo_raw = &$headerfooterinfo['raw'];
@ -389,6 +390,7 @@ class getid3_apetag extends getid3_handler
// "Note: APE Tags 1.0 do not use any of the APE Tag flags. // "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." // All are set to zero on creation and ignored on reading."
// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags // http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
$flags = array();
$flags['header'] = (bool) ($rawflagint & 0x80000000); $flags['header'] = (bool) ($rawflagint & 0x80000000);
$flags['footer'] = (bool) ($rawflagint & 0x40000000); $flags['footer'] = (bool) ($rawflagint & 0x40000000);
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);

View File

@ -31,14 +31,22 @@ class getid3_id3v1 extends getid3_handler
return false; return false;
} }
if($info['filesize'] < 256) {
$this->fseek(-128, SEEK_END);
$preid3v1 = '';
$id3v1tag = $this->fread(128);
} else {
$this->fseek(-256, SEEK_END); $this->fseek(-256, SEEK_END);
$preid3v1 = $this->fread(128); $preid3v1 = $this->fread(128);
$id3v1tag = $this->fread(128); $id3v1tag = $this->fread(128);
}
if (substr($id3v1tag, 0, 3) == 'TAG') { if (substr($id3v1tag, 0, 3) == 'TAG') {
$info['avdataend'] = $info['filesize'] - 128; $info['avdataend'] = $info['filesize'] - 128;
$ParsedID3v1 = array();
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30)); $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
@ -297,6 +305,50 @@ class getid3_id3v1 extends getid3_handler
145 => 'Anime', 145 => 'Anime',
146 => 'JPop', 146 => 'JPop',
147 => 'Synthpop', 147 => 'Synthpop',
148 => 'Abstract',
149 => 'Art Rock',
150 => 'Baroque',
151 => 'Bhangra',
152 => 'Big Beat',
153 => 'Breakbeat',
154 => 'Chillout',
155 => 'Downtempo',
156 => 'Dub',
157 => 'EBM',
158 => 'Eclectic',
159 => 'Electro',
160 => 'Electroclash',
161 => 'Emo',
162 => 'Experimental',
163 => 'Garage',
164 => 'Global',
165 => 'IDM',
166 => 'Illbient',
167 => 'Industro-Goth',
168 => 'Jam Band',
169 => 'Krautrock',
170 => 'Leftfield',
171 => 'Lounge',
172 => 'Math Rock',
173 => 'New Romantic',
174 => 'Nu-Breakz',
175 => 'Post-Punk',
176 => 'Post-Rock',
177 => 'Psytrance',
178 => 'Shoegaze',
179 => 'Space Rock',
180 => 'Trop Rock',
181 => 'World Music',
182 => 'Neoclassical',
183 => 'Audiobook',
184 => 'Audio Theatre',
185 => 'Neue Deutsche Welle',
186 => 'Podcast',
187 => 'Indie-Rock',
188 => 'G-Funk',
189 => 'Dubstep',
190 => 'Garage Rock',
191 => 'Psybient',
255 => 'Unknown', 255 => 'Unknown',

View File

@ -345,7 +345,7 @@ class getid3_id3v2 extends getid3_handler
} }
if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
unset($parsedFrame); $parsedFrame = array();
$parsedFrame['frame_name'] = $frame_name; $parsedFrame['frame_name'] = $frame_name;
$parsedFrame['frame_flags_raw'] = $frame_flags; $parsedFrame['frame_flags_raw'] = $frame_flags;
$parsedFrame['data'] = substr($framedata, 0, $frame_size); $parsedFrame['data'] = substr($framedata, 0, $frame_size);
@ -994,6 +994,7 @@ class getid3_id3v2 extends getid3_handler
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00"; $frame_textencoding_terminator = "\x00";
} }
if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) { // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
$frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3; $frame_offset += 3;
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
@ -1013,6 +1014,9 @@ class getid3_id3v2 extends getid3_handler
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
} }
} else {
$this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
}
unset($parsedFrame['data']); unset($parsedFrame['data']);
@ -1370,6 +1374,8 @@ class getid3_id3v2 extends getid3_handler
$frame_textencoding_terminator = "\x00"; $frame_textencoding_terminator = "\x00";
} }
$frame_imagetype = null;
$frame_mimetype = null;
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
if (strtolower($frame_imagetype) == 'ima') { if (strtolower($frame_imagetype) == 'ima') {
@ -1956,18 +1962,14 @@ class getid3_id3v2 extends getid3_handler
$frame_offset = 0; $frame_offset = 0;
$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4; $frame_offset += 4;
$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); foreach (array('track','album') as $rgad_entry_type) {
$rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2; $frame_offset += 2;
$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); $parsedFrame['raw'][$rgad_entry_type]['name'] = ($rg_adjustment_word & 0xE000) >> 13;
$frame_offset += 2; $parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
$parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); $parsedFrame['raw'][$rgad_entry_type]['signbit'] = ($rg_adjustment_word & 0x0200) >> 9;
$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); $parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
$parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); }
$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
$parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
$parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
$parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
@ -2444,7 +2446,8 @@ class getid3_id3v2 extends getid3_handler
TMM Manats TMM Manats
TND Dinars TND Dinars
TOP Pa'anga TOP Pa'anga
TRL Liras TRL Liras (old)
TRY Liras
TTD Dollars TTD Dollars
TVD Tuvalu Dollars TVD Tuvalu Dollars
TWD New Dollars TWD New Dollars
@ -2645,6 +2648,7 @@ class getid3_id3v2 extends getid3_handler
TND Tunisia TND Tunisia
TOP Tonga TOP Tonga
TRL Turkey TRL Turkey
TRY Turkey
TTD Trinidad and Tobago TTD Trinidad and Tobago
TVD Tuvalu TVD Tuvalu
TWD Taiwan TWD Taiwan

View File

@ -33,6 +33,9 @@ class getid3_lyrics3 extends getid3_handler
} }
$this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] $this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
$lyrics3offset = null;
$lyrics3version = null;
$lyrics3size = null;
$lyrics3_id3v1 = $this->fread(128 + 9 + 6); $lyrics3_id3v1 = $this->fread(128 + 9 + 6);
$lyrics3lsz = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size $lyrics3lsz = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200

View File

@ -625,3 +625,4 @@ Reference material:
* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
* http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header * http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
* http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf * http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
* https://fileformats.fandom.com/wiki/Torrent_file

View File

@ -16,7 +16,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '5.9-alpha-52253'; $wp_version = '5.9-alpha-52254';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.