diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index dbeea195..7b5543a7 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -21,8 +21,9 @@ jobs:
- "8.0"
- "8.1"
- "8.2"
+ - "8.3"
steps:
- - uses: "actions/checkout@v2"
+ - uses: "actions/checkout@v3"
- uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
@@ -36,11 +37,11 @@ jobs:
name: "Static Analysis"
runs-on: "ubuntu-latest"
steps:
- - uses: "actions/checkout@v2"
+ - uses: "actions/checkout@v3"
- uses: "shivammathur/setup-php@v2"
with:
php-version: "7.4"
- tools: "phpstan:0.12.99"
+ tools: "phpstan:1.8.11"
coverage: "none"
- uses: "ramsey/composer-install@v2"
- name: "Run PHPStan"
diff --git a/LICENSE.md b/LICENSE.md
index 4e5f0658..0bdeea29 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -24,4 +24,4 @@ Mozilla MPL
getID3 Commercial License
-------------------------
-* [gCL](http://getid3.org/#gCL) (payment required)
+* [gCL](http://getid3.org/#gCL) (no longer available, existing licenses remain valid)
diff --git a/changelog.txt b/changelog.txt
index e6a6e586..488e4c1d 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -18,6 +18,19 @@
Version History
===============
+1.9.23: [2023-10-19] James Heinrich :: 1.9.23-202310190849
+ ยป add detection support for 7-zip archives
+ * #424 RIFF Undefined index "data"
+ * #421 tag.xmp remove GLOBALS
+ * #419 Quicktime Undefined index "time_scale"
+ * #418 tag.xmp zero-length fread
+ * #414 Quicktime bitrate for mp4 audio
+ * #413 Quicktime audio metadata
+ * #410 MPEG-1 pixel aspect ratio
+ * #407 PHP 8.1 compatibility
+ * #404 guard against division by zero
+ * #402 remove utf8_encode/utf8_decode
+
1.9.22: [2022-09-29] James Heinrich :: 1.9.22-202207161647
* bugfix #387 fails to detect h265 video codec (QuickTime)
* bugfix #385 Quicktime extended atom size
diff --git a/demos/demo.browse.php b/demos/demo.browse.php
index 4a1739dd..eb4cb1e1 100644
--- a/demos/demo.browse.php
+++ b/demos/demo.browse.php
@@ -460,7 +460,7 @@ function table_var_dump($variable, $wrap_in_td=false, $encoding='') {
global $FileSystemEncoding;
$encoding = ($encoding ? $encoding : $FileSystemEncoding);
$returnstring = '';
- switch (gettype($variable)) {
+ switch (strtolower(gettype($variable))) {
case 'array':
$returnstring .= ($wrap_in_td ? '
' : '');
$returnstring .= '';
diff --git a/demos/demo.mp3header.php b/demos/demo.mp3header.php
index 222fe40e..57c241c8 100644
--- a/demos/demo.mp3header.php
+++ b/demos/demo.mp3header.php
@@ -705,7 +705,7 @@ function RoughTranslateUnicodeToASCII($rawdata, $frame_textencoding) {
break;
case 3: // UTF-8 encoded Unicode. Terminated with $00.
- $asciidata = utf8_decode($rawdata);
+ $asciidata = utf8_to_iso8859_1($rawdata);
break;
case 255: // Unicode, Big-Endian. Terminated with $00 00.
@@ -929,43 +929,40 @@ function image_type_to_mime_type($imagetypeid) {
}
}
-if (!function_exists('utf8_decode')) {
- // PHP has this function built-in if it's configured with the --with-xml option
- // This version of the function is only provided in case XML isn't installed
- function utf8_decode($utf8text) {
- // http://www.php.net/manual/en/function.utf8-encode.php
- // bytes bits representation
- // 1 7 0bbbbbbb
- // 2 11 110bbbbb 10bbbbbb
- // 3 16 1110bbbb 10bbbbbb 10bbbbbb
- // 4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
-
- $utf8length = strlen($utf8text);
- $decodedtext = '';
- for ($i = 0; $i < $utf8length; $i++) {
- if ((ord($utf8text[$i]) & 0x80) == 0) {
- $decodedtext .= $utf8text[$i];
- } elseif ((ord($utf8text[$i]) & 0xF0) == 0xF0) {
- $decodedtext .= '?';
- $i += 3;
- } elseif ((ord($utf8text[$i]) & 0xE0) == 0xE0) {
+function utf8_to_iso8859_1($utf8text) {
+ // http://www.php.net/manual/en/function.utf8-encode.php
+ // bytes bits representation
+ // 1 7 0bbbbbbb
+ // 2 11 110bbbbb 10bbbbbb
+ // 3 16 1110bbbb 10bbbbbb 10bbbbbb
+ // 4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
+
+ $utf8length = strlen($utf8text);
+ $decodedtext = '';
+ for ($i = 0; $i < $utf8length; $i++) {
+ if ((ord($utf8text[$i]) & 0x80) == 0) {
+ $decodedtext .= $utf8text[$i];
+ } elseif ((ord($utf8text[$i]) & 0xF0) == 0xF0) {
+ $decodedtext .= '?';
+ $i += 3;
+ } elseif ((ord($utf8text[$i]) & 0xE0) == 0xE0) {
+ $decodedtext .= '?';
+ $i += 2;
+ } elseif ((ord($utf8text[$i]) & 0xC0) == 0xC0) {
+ // 2 11 110bbbbb 10bbbbbb
+ $decodedchar = Bin2Dec(substr(Dec2Bin(ord($utf8text[$i])), 3, 5).substr(Dec2Bin(ord($utf8text[($i + 1)])), 2, 6));
+ if ($decodedchar <= 255) {
+ $decodedtext .= chr($decodedchar);
+ } else {
$decodedtext .= '?';
- $i += 2;
- } elseif ((ord($utf8text[$i]) & 0xC0) == 0xC0) {
- // 2 11 110bbbbb 10bbbbbb
- $decodedchar = Bin2Dec(substr(Dec2Bin(ord($utf8text[$i])), 3, 5).substr(Dec2Bin(ord($utf8text[($i + 1)])), 2, 6));
- if ($decodedchar <= 255) {
- $decodedtext .= chr($decodedchar);
- } else {
- $decodedtext .= '?';
- }
- $i += 1;
}
+ $i += 1;
}
- return $decodedtext;
}
+ return $decodedtext;
}
+
if (!function_exists('DateMac2Unix')) {
function DateMac2Unix($macdate) {
// Macintosh timestamp: seconds since 00:00h January 1, 1904
diff --git a/readme.txt b/readme.txt
index 1f79d75d..6d40e1cf 100644
--- a/readme.txt
+++ b/readme.txt
@@ -20,7 +20,8 @@ GNU LGPL: https://gnu.org/licenses/lgpl.html (v3)
Mozilla MPL: https://www.mozilla.org/MPL/2.0/ (v2)
-getID3 Commercial License: https://www.getid3.org/#gCL (payment required)
+getID3 Commercial License: https://www.getid3.org/#gCL
+(no longer available, existing licenses remain valid)
*****************************************************************
*****************************************************************
diff --git a/src/GetID3.php b/src/GetID3.php
index 5b884919..13b0ddef 100644
--- a/src/GetID3.php
+++ b/src/GetID3.php
@@ -319,7 +319,7 @@ class GetID3
*/
protected $startup_warning = '';
- const VERSION = '2.0.x-202207161647';
+ const VERSION = '2.0.x-202310190849';
const FREAD_BUFFER_SIZE = 32768;
const ATTACHMENTS_NONE = false;
@@ -1253,6 +1253,15 @@ public function GetFileFormatArray() {
'fail_ape' => 'ERROR',
),
+ // XZ - data - XZ compressed data
+ '7zip' => array(
+ 'pattern' => '^7z\\xBC\\xAF\\x27\\x1C',
+ 'module' => 'Archive\\SevenZip',
+ 'mime_type' => 'application/x-7z-compressed',
+ 'fail_id3' => 'ERROR',
+ 'fail_ape' => 'ERROR',
+ ),
+
// Misc other formats
@@ -1750,7 +1759,7 @@ public function CalculateCompressionRatioVideo() {
}
$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
- $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
+ $this->info['video']['compression_ratio'] = Utils::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1);
return true;
}
diff --git a/src/Module/Archive/Hpk.php b/src/Module/Archive/Hpk.php
index b72e3e85..b223fbcb 100644
--- a/src/Module/Archive/Hpk.php
+++ b/src/Module/Archive/Hpk.php
@@ -44,7 +44,7 @@ public function Analyze() {
$info['hpk']['header']['fragmented_filesystem_offset'] = Utils::LittleEndian2Int(substr($HPKheader, 28, 4));
$info['hpk']['header']['fragmented_filesystem_length'] = Utils::LittleEndian2Int(substr($HPKheader, 32, 4));
- $info['hpk']['header']['filesystem_entries'] = $info['hpk']['header']['fragmented_filesystem_length'] / ($info['hpk']['header']['fragments_per_file'] * 8);
+ $info['hpk']['header']['filesystem_entries'] = Utils::SafeDiv($info['hpk']['header']['fragmented_filesystem_length'], $info['hpk']['header']['fragments_per_file'] * 8);
$this->fseek($info['hpk']['header']['fragmented_filesystem_offset']);
for ($i = 0; $i < $info['hpk']['header']['filesystem_entries']; $i++) {
$offset = Utils::LittleEndian2Int($this->fread(4));
diff --git a/src/Module/Archive/SevenZip.php b/src/Module/Archive/SevenZip.php
new file mode 100644
index 00000000..22120846
--- /dev/null
+++ b/src/Module/Archive/SevenZip.php
@@ -0,0 +1,52 @@
+ //
+// available at https://github.com/JamesHeinrich/getID3 //
+// or https://www.getid3.org //
+// or http://getid3.sourceforge.net //
+// see readme.txt for more details //
+/////////////////////////////////////////////////////////////////
+// //
+// module.archive.7zip.php //
+// module for analyzing 7zip files //
+// ///
+/////////////////////////////////////////////////////////////////
+
+class SevenZip extends Handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $z7header = $this->fread(32);
+
+ // https://py7zr.readthedocs.io/en/latest/archive_format.html
+ $info['7zip']['header']['magic'] = substr($z7header, 0, 6);
+ if ($info['7zip']['header']['magic'] != '7z'."\xBC\xAF\x27\x1C") {
+ $this->error('Invalid 7zip stream header magic (expecting 37 7A BC AF 27 1C, found '.Utils::PrintHexBytes($info['7zip']['header']['magic']).') at offset '.$info['avdataoffset']);
+ return false;
+ }
+ $info['fileformat'] = '7zip';
+
+ $info['7zip']['header']['version_major'] = Utils::LittleEndian2Int(substr($z7header, 6, 1)); // always 0x00 (?)
+ $info['7zip']['header']['version_minor'] = Utils::LittleEndian2Int(substr($z7header, 7, 1)); // always 0x04 (?)
+ $info['7zip']['header']['start_header_crc'] = Utils::LittleEndian2Int(substr($z7header, 8, 4));
+ $info['7zip']['header']['next_header_offset'] = Utils::LittleEndian2Int(substr($z7header, 12, 8));
+ $info['7zip']['header']['next_header_size'] = Utils::LittleEndian2Int(substr($z7header, 20, 8));
+ $info['7zip']['header']['next_header_crc'] = Utils::LittleEndian2Int(substr($z7header, 28, 4));
+
+$this->error('7zip parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
+ return false;
+
+ }
+
+}
diff --git a/src/Module/Audio/Amr.php b/src/Module/Audio/Amr.php
index 873742bf..f517505d 100644
--- a/src/Module/Audio/Amr.php
+++ b/src/Module/Audio/Amr.php
@@ -63,7 +63,7 @@ public function Analyze() {
} while (strlen($buffer) > 0);
$info['playtime_seconds'] = array_sum($thisfile_amr['frame_mode_count']) * 0.020; // each frame contain 160 samples and is 20 milliseconds long
- $info['audio']['bitrate'] = (8 * ($info['avdataend'] - $info['avdataoffset'])) / $info['playtime_seconds']; // bitrate could be calculated from average bitrate by distributation of frame types. That would give effective audio bitrate, this gives overall file bitrate which will be a little bit higher since every frame will waste 8 bits for header, plus a few bits for octet padding
+ $info['audio']['bitrate'] = Utils::SafeDiv(8 * ($info['avdataend'] - $info['avdataoffset']), $info['playtime_seconds']); // bitrate could be calculated from average bitrate by distributation of frame types. That would give effective audio bitrate, this gives overall file bitrate which will be a little bit higher since every frame will waste 8 bits for header, plus a few bits for octet padding
$info['bitrate'] = $info['audio']['bitrate'];
return true;
diff --git a/src/Module/Audio/Au.php b/src/Module/Audio/Au.php
index 8632c85b..8ce73489 100644
--- a/src/Module/Audio/Au.php
+++ b/src/Module/Audio/Au.php
@@ -69,8 +69,8 @@ public function Analyze() {
$this->warning('Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"');
}
- $info['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8));
- $info['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $info['playtime_seconds'];
+ $info['audio']['bitrate'] = $thisfile_au['sample_rate'] * $thisfile_au['channels'] * $thisfile_au['used_bits_per_sample'];
+ $info['playtime_seconds'] = Utils::SafeDiv($thisfile_au['data_size'], $info['audio']['bitrate'] / 8);
return true;
}
diff --git a/src/Module/Audio/Avr.php b/src/Module/Audio/Avr.php
index 8de0b52e..83ed8c34 100644
--- a/src/Module/Audio/Avr.php
+++ b/src/Module/Audio/Avr.php
@@ -120,9 +120,9 @@ public function Analyze() {
$info['audio']['bits_per_sample'] = $info['avr']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['avr']['sample_rate'];
$info['audio']['channels'] = ($info['avr']['flags']['stereo'] ? 2 : 1);
- $info['playtime_seconds'] = ($info['avr']['sample_length'] / $info['audio']['channels']) / $info['avr']['sample_rate'];
- $info['audio']['bitrate'] = ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 8 : 16)) / $info['playtime_seconds'];
-
+ $bits_per_sample = ($info['avr']['bits_per_sample'] == 8) ? 8 : 16;
+ $info['audio']['bitrate'] = $bits_per_sample * $info['audio']['channels'] * $info['avr']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['avr']['sample_length'] * $bits_per_sample, $info['audio']['bitrate']);
return true;
}
diff --git a/src/Module/Audio/Bonk.php b/src/Module/Audio/Bonk.php
index 2be7a141..62bb0f6f 100644
--- a/src/Module/Audio/Bonk.php
+++ b/src/Module/Audio/Bonk.php
@@ -156,7 +156,7 @@ public function HandleBonkTags($BonkTagName) {
$info['audio']['lossless'] = $thisfile_bonk_BONK['lossless'];
$info['audio']['codec'] = 'bonk';
- $info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']);
+ $info['playtime_seconds'] = Utils::SafeDiv($thisfile_bonk_BONK['number_samples'], $thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']);
if ($info['playtime_seconds'] > 0) {
$info['audio']['bitrate'] = (($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8) / $info['playtime_seconds'];
}
diff --git a/src/Module/Audio/Dsdiff.php b/src/Module/Audio/Dsdiff.php
index 6c37db2e..01a8f340 100644
--- a/src/Module/Audio/Dsdiff.php
+++ b/src/Module/Audio/Dsdiff.php
@@ -198,9 +198,13 @@ public function Analyze() {
$this->fseek(1, SEEK_CUR);
}
- if ($commentkey = (($thisChunk['name'] == 'DIAR') ? 'artist' : (($thisChunk['name'] == 'DITI') ? 'title' : ''))) {
- @$info['dsdiff']['comments'][$commentkey][] = $thisChunk['description'];
- }
+ $commentKeys = array(
+ 'DIAR' => 'artist',
+ 'DITI' => 'title'
+ );
+ $commentkey = $commentKeys[$thisChunk['name']];
+
+ $info['dsdiff']['comments'][$commentkey][] = $thisChunk['description'];
break;
case 'EMID': // Edited Master ID chunk
if ($thisChunk['size']) {
diff --git a/src/Module/Audio/Dsf.php b/src/Module/Audio/Dsf.php
index 215c320b..20bdb6aa 100644
--- a/src/Module/Audio/Dsf.php
+++ b/src/Module/Audio/Dsf.php
@@ -118,7 +118,7 @@ public function Analyze() {
$info['audio']['sample_rate'] = $info['dsf']['fmt']['sample_rate'];
$info['audio']['channels'] = $info['dsf']['fmt']['channels'];
$info['audio']['bitrate'] = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels'];
- $info['playtime_seconds'] = ($info['dsf']['data']['data_chunk_size'] * 8) / $info['audio']['bitrate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['dsf']['data']['data_chunk_size'] * 8, $info['audio']['bitrate']);
return true;
}
diff --git a/src/Module/Audio/Lpac.php b/src/Module/Audio/Lpac.php
index f2392e50..89c895a8 100644
--- a/src/Module/Audio/Lpac.php
+++ b/src/Module/Audio/Lpac.php
@@ -127,8 +127,8 @@ public function Analyze() {
}
}
- $info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate'];
- $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['lpac']['total_samples'], $info['audio']['sample_rate']);
+ $info['audio']['bitrate'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);
return true;
}
diff --git a/src/Module/Audio/Midi.php b/src/Module/Audio/Midi.php
index 17b5756e..aeb85afa 100644
--- a/src/Module/Audio/Midi.php
+++ b/src/Module/Audio/Midi.php
@@ -106,7 +106,6 @@ public function Analyze() {
$thisfile_midi['totalticks'] = 0;
$info['playtime_seconds'] = 0;
$CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
- $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
$MicroSecondsPerQuarterNoteAfter = array ();
$MIDIevents = array();
@@ -251,7 +250,6 @@ public function Analyze() {
return false;
}
$thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
- $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
$MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
$TicksAtCurrentBPM = 0;
break;
diff --git a/src/Module/Audio/Mp3.php b/src/Module/Audio/Mp3.php
index 2f565917..c64ebe61 100644
--- a/src/Module/Audio/Mp3.php
+++ b/src/Module/Audio/Mp3.php
@@ -1380,11 +1380,11 @@ public function getOnlyMPEGaudioInfoBruteForce() {
$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1;
$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1;
if (++$frames_scanned >= $max_frames_scan) {
- $pct_data_scanned = ($this->ftell() - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
+ $pct_data_scanned = Utils::SafeDiv($this->ftell() - $info['avdataoffset'], $info['avdataend'] - $info['avdataoffset']);
$this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
foreach ($Distribution as $key1 => $value1) {
foreach ($value1 as $key2 => $value2) {
- $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
+ $Distribution[$key1][$key2] = $pct_data_scanned ? round($value2 / $pct_data_scanned) : 1;
}
}
break;
@@ -1475,7 +1475,7 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
$SyncSeekAttemptsMax = 1000;
$FirstFrameThisfileInfo = null;
while ($SynchSeekOffset < $sync_seek_buffer_size) {
- if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
+ if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !$this->feof()) {
if ($SynchSeekOffset > $sync_seek_buffer_size) {
// if a synch's not found within the first 128k bytes, then give up
@@ -1490,20 +1490,6 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
unset($info['mpeg']);
}
return false;
-
- } elseif (feof($this->getid3->fp)) {
-
- $this->error('Could not find valid MPEG audio synch before end of file');
- if (isset($info['audio']['bitrate'])) {
- unset($info['audio']['bitrate']);
- }
- if (isset($info['mpeg']['audio'])) {
- unset($info['mpeg']['audio']);
- }
- if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) {
- unset($info['mpeg']);
- }
- return false;
}
}
@@ -1652,7 +1638,7 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
}
$frames_scanned++;
if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
- $this_pct_scanned = ($this->ftell() - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']);
+ $this_pct_scanned = Utils::SafeDiv($this->ftell() - $scan_start_offset[$current_segment], $info['avdataend'] - $info['avdataoffset']);
if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
// file likely contains < $max_frames_scan, just scan as one segment
$max_scan_segments = 1;
@@ -1743,6 +1729,10 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
}
$info['audio']['channels'] = $info['mpeg']['audio']['channels'];
+ if ($info['audio']['channels'] < 1) {
+ $this->error('Corrupt MP3 file: no channels');
+ return false;
+ }
$info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode'];
$info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
return true;
diff --git a/src/Module/Audio/Mpc.php b/src/Module/Audio/Mpc.php
index c6dc0be6..9afff8a8 100644
--- a/src/Module/Audio/Mpc.php
+++ b/src/Module/Audio/Mpc.php
@@ -137,7 +137,7 @@ public function ParseMPCsv8() {
$info['audio']['channels'] = $thisPacket['channels'];
$info['audio']['sample_rate'] = $thisPacket['sample_frequency'];
$info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency'];
- $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
+ $info['audio']['bitrate'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);
break;
case 'RG': // Replay Gain
@@ -282,7 +282,7 @@ public function ParseMPCsv7() {
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
$thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels'];
- $info['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $info['audio']['channels']) / $info['audio']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($thisfile_mpc_header['samples'], $info['audio']['channels'] * $info['audio']['sample_rate']);
if ($info['playtime_seconds'] == 0) {
$this->error('Corrupt MPC file: playtime_seconds == zero');
return false;
diff --git a/src/Module/Audio/Ogg.php b/src/Module/Audio/Ogg.php
index 9cfd9c4e..23a1cd62 100644
--- a/src/Module/Audio/Ogg.php
+++ b/src/Module/Audio/Ogg.php
@@ -209,8 +209,8 @@ public function Analyze() {
$filedataoffset += 20;
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
- $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
- $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
+ $info['ogg']['skeleton']['fishead']['presentationtime'] = Utils::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']);
+ $info['ogg']['skeleton']['fishead']['basetime'] = Utils::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']);
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
@@ -287,7 +287,7 @@ public function Analyze() {
$info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate'];
$info['audio']['channels'] = $info['flac']['STREAMINFO']['channels'];
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
- $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['flac']['STREAMINFO']['samples_stream'], $info['flac']['STREAMINFO']['sample_rate']);
}
} else {
@@ -358,7 +358,7 @@ public function Analyze() {
return false;
}
if (!empty($info['audio']['sample_rate'])) {
- $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
+ $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) * $info['audio']['sample_rate'] / $info['ogg']['samples'];
}
}
@@ -533,12 +533,12 @@ public function ParseOggPageHeader() {
$filedata = $this->fread($this->getid3->fread_buffer_size());
$filedataoffset = 0;
- while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
+ while (substr($filedata, $filedataoffset++, 4) != 'OggS') {
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
// should be found before here
return false;
}
- if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
+ if (($filedataoffset + 28) > strlen($filedata)) {
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
// get some more data, unless eof, in which case fail
return false;
diff --git a/src/Module/Audio/Rkau.php b/src/Module/Audio/Rkau.php
index 17c64b12..cb41f083 100644
--- a/src/Module/Audio/Rkau.php
+++ b/src/Module/Audio/Rkau.php
@@ -75,7 +75,7 @@ public function Analyze() {
$info['audio']['sample_rate'] = $info['rkau']['sample_rate'];
$info['playtime_seconds'] = $info['rkau']['source_bytes'] / ($info['rkau']['sample_rate'] * $info['rkau']['channels'] * ($info['rkau']['bits_per_sample'] / 8));
- $info['audio']['bitrate'] = ($info['rkau']['compressed_bytes'] * 8) / $info['playtime_seconds'];
+ $info['audio']['bitrate'] = Utils::SafeDiv($info['rkau']['compressed_bytes'] * 8, $info['playtime_seconds']);
return true;
diff --git a/src/Module/Audio/Shorten.php b/src/Module/Audio/Shorten.php
index e4cc6d9c..19db81ed 100644
--- a/src/Module/Audio/Shorten.php
+++ b/src/Module/Audio/Shorten.php
@@ -156,7 +156,7 @@ public function Analyze() {
if (substr($output, 20 + $fmt_size, 4) == 'data') {
- $info['playtime_seconds'] = Utils::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec'];
+ $info['playtime_seconds'] = Utils::SafeDiv(Utils::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)), $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']);
} else {
@@ -165,7 +165,7 @@ public function Analyze() {
}
- $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8;
+ $info['audio']['bitrate'] = Utils::SafeDiv($info['avdataend'] - $info['avdataoffset'], $info['playtime_seconds']) * 8;
} else {
diff --git a/src/Module/Audio/Tta.php b/src/Module/Audio/Tta.php
index f769468f..aa8534c4 100644
--- a/src/Module/Audio/Tta.php
+++ b/src/Module/Audio/Tta.php
@@ -59,7 +59,7 @@ public function Analyze() {
$info['tta']['samples_per_channel'] = Utils::LittleEndian2Int(substr($ttaheader, 12, 4));
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
- $info['playtime_seconds'] = $info['tta']['samples_per_channel'] / $info['tta']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['tta']['samples_per_channel'], $info['tta']['sample_rate']);
break;
case '2': // TTA v2.x
@@ -75,7 +75,7 @@ public function Analyze() {
$info['tta']['data_length'] = Utils::LittleEndian2Int(substr($ttaheader, 16, 4));
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
- $info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['tta']['data_length'], $info['tta']['sample_rate']);
break;
case '1': // TTA v3.x
@@ -91,7 +91,7 @@ public function Analyze() {
$info['tta']['crc32_footer'] = substr($ttaheader, 18, 4);
$info['tta']['seek_point'] = Utils::LittleEndian2Int(substr($ttaheader, 22, 4));
- $info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['tta']['data_length'], $info['tta']['sample_rate']);
break;
default:
@@ -103,7 +103,7 @@ public function Analyze() {
$info['audio']['bits_per_sample'] = $info['tta']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['tta']['sample_rate'];
$info['audio']['channels'] = $info['tta']['channels'];
- $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
+ $info['audio']['bitrate'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);
return true;
}
diff --git a/src/Module/Audio/Voc.php b/src/Module/Audio/Voc.php
index 447e1472..df6a5c4f 100644
--- a/src/Module/Audio/Voc.php
+++ b/src/Module/Audio/Voc.php
@@ -140,7 +140,7 @@ public function Analyze() {
$thisfile_audio['sample_rate'] = $ThisBlock['sample_rate'];
$thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample'];
- $thisfile_audio['channels'] = $ThisBlock['channels'];
+ $thisfile_audio['channels'] = $ThisBlock['channels'] ?: 1;
break;
default:
@@ -164,8 +164,8 @@ public function Analyze() {
ksort($thisfile_voc['blocktypes']);
if (!empty($thisfile_voc['compressed_bits_per_sample'])) {
- $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']);
- $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
+ $info['playtime_seconds'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']);
+ $thisfile_audio['bitrate'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);
}
return true;
diff --git a/src/Module/Audio/WavPack.php b/src/Module/Audio/WavPack.php
index cfc3f409..ac926e1f 100644
--- a/src/Module/Audio/WavPack.php
+++ b/src/Module/Audio/WavPack.php
@@ -45,7 +45,7 @@ public function Analyze() {
if ($this->ftell() >= $info['avdataend']) {
break;
- } elseif (feof($this->getid3->fp)) {
+ } elseif ($this->feof()) {
break;
} elseif (
isset($info['wavpack']['blockheader']['total_samples']) &&
@@ -160,11 +160,11 @@ public function Analyze() {
$info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid'];
}
- while (!feof($this->getid3->fp) && ($this->ftell() < ($blockheader_offset + $blockheader_size + 8))) {
+ while (!$this->feof() && ($this->ftell() < ($blockheader_offset + $blockheader_size + 8))) {
$metablock = array('offset'=>$this->ftell());
$metablockheader = $this->fread(2);
- if (feof($this->getid3->fp)) {
+ if ($this->feof()) {
break;
}
$metablock['id'] = ord($metablockheader[0]);
@@ -245,7 +245,7 @@ public function Analyze() {
$metablock['riff']['original_filesize'] = $original_wav_filesize;
$info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size'];
- $info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate'];
+ $info['playtime_seconds'] = Utils::SafeDiv($info['wavpack']['blockheader']['total_samples'], $info['audio']['sample_rate']);
// Safe RIFF header in case there's a RIFF footer later
$metablockRIFFheader = $metablock['data'];
diff --git a/src/Module/AudioVideo/Asf.php b/src/Module/AudioVideo/Asf.php
index 67c4ad96..de6f4572 100644
--- a/src/Module/AudioVideo/Asf.php
+++ b/src/Module/AudioVideo/Asf.php
@@ -195,7 +195,7 @@ public function Analyze() {
$info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000);
//$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate'];
- $info['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8) / $info['playtime_seconds'];
+ $info['bitrate'] = Utils::SafeDiv($thisfile_asf_filepropertiesobject['filesize'] * 8, $info['playtime_seconds']);
}
break;
@@ -1066,7 +1066,7 @@ public function Analyze() {
break;
}
- if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) {
+ if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { // @phpstan-ignore-line
foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) {
if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
$thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate'];
@@ -1152,7 +1152,7 @@ public function Analyze() {
$videomediaoffset += 4;
$thisfile_asf_videomedia_currentstream['format_data']['codec_data'] = substr($streamdata['type_specific_data'], $videomediaoffset);
- if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) {
+ if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { // @phpstan-ignore-line
foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) {
if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
$thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate'];
diff --git a/src/Module/AudioVideo/Ivf.php b/src/Module/AudioVideo/Ivf.php
index bf775fff..f139cd8e 100644
--- a/src/Module/AudioVideo/Ivf.php
+++ b/src/Module/AudioVideo/Ivf.php
@@ -47,7 +47,7 @@ public function Analyze() {
$info['ivf']['header']['frame_count'] = Utils::LittleEndian2Int(substr($IVFheader, 24, 4));
//$info['ivf']['header']['reserved'] = substr($IVFheader, 28, 4);
- $info['ivf']['header']['frame_rate'] = (float) $info['ivf']['header']['timebase_numerator'] / $info['ivf']['header']['timebase_denominator'];
+ $info['ivf']['header']['frame_rate'] = (float)Utils::SafeDiv($info['ivf']['header']['timebase_numerator'], $info['ivf']['header']['timebase_denominator']);
if ($info['ivf']['header']['version'] > 0) {
$this->warning('Expecting IVF header version 0, found version '.$info['ivf']['header']['version'].', results may not be accurate');
@@ -67,7 +67,7 @@ public function Analyze() {
$info['ivf']['frame_count']++;
}
}
- if ($info['ivf']['frame_count']) {
+ if ($info['ivf']['frame_count'] && $info['playtime_seconds']) {
$info['playtime_seconds'] = $timestamp / 100000;
$info['video']['frame_rate'] = (float) $info['ivf']['frame_count'] / $info['playtime_seconds'];
}
diff --git a/src/Module/AudioVideo/Matroska.php b/src/Module/AudioVideo/Matroska.php
index 944734e8..4b0b345b 100644
--- a/src/Module/AudioVideo/Matroska.php
+++ b/src/Module/AudioVideo/Matroska.php
@@ -298,12 +298,12 @@ public function Analyze()
$track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']);
$track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']);
- if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; }
- if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; }
- if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; }
- if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; }
- if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); }
- if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; }
+ if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; }
+ if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; }
+ if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; }
+ if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; }
+ if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); }
+ if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; }
switch ($trackarray['CodecID']) {
case 'V_MS/VFW/FOURCC':
diff --git a/src/Module/AudioVideo/Mpeg.php b/src/Module/AudioVideo/Mpeg.php
index 0782aafa..fe24265b 100644
--- a/src/Module/AudioVideo/Mpeg.php
+++ b/src/Module/AudioVideo/Mpeg.php
@@ -505,9 +505,9 @@ public function Analyze() {
$last_GOP_id = max(array_keys($FramesByGOP));
$frames_in_last_GOP = count($FramesByGOP[$last_GOP_id]);
$gopdata = &$info['mpeg']['group_of_pictures'][$last_GOP_id];
- $info['playtime_seconds'] = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + (($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1) / $info['mpeg']['video']['frame_rate']);
+ $info['playtime_seconds'] = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + Utils::SafeDiv($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1, $info['mpeg']['video']['frame_rate']);
if (!isset($info['video']['bitrate'])) {
- $overall_bitrate = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
+ $overall_bitrate = Utils::SafeDiv($info['avdataend'] - $info['avdataoffset'] * 8, $info['playtime_seconds']);
$info['video']['bitrate'] = $overall_bitrate - (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0);
}
unset($info['mpeg']['group_of_pictures']);
@@ -610,14 +610,20 @@ public static function videoFramerateLookup($rawframerate) {
* @return float
*/
public static function videoAspectRatioLookup($rawaspectratio, $mpeg_version=1, $width=0, $height=0) {
+ // Per http://forum.doom9.org/archive/index.php/t-84400.html
+ // 0.9157 is commonly accepted to mean 11/12 or .9166, the reciprocal of 12/11 (1.091) and,
+ // 1.0950 is commonly accepted to mean 11/10 or 1.1, the reciprocal of 10/11 (0.909)
$lookup = array(
- 1 => array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0),
+ 1 => array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 11/12, 0.9815, 1.0255, 1.0695, 11/10, 1.1575, 1.2015, 0),
2 => array(0, 1, 1.3333, 1.7778, 2.2100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
);
$ratio = (float) (isset($lookup[$mpeg_version][$rawaspectratio]) ? $lookup[$mpeg_version][$rawaspectratio] : 0);
if ($mpeg_version == 2 && $ratio != 1 && $width != 0) {
// Calculate pixel aspect ratio from MPEG-2 display aspect ratio
$ratio = $ratio * $height / $width;
+ } else if ($mpeg_version == 1 && $ratio !== 0.0) {
+ // The MPEG-1 tables store the reciprocal of the pixel aspect ratio.
+ $ratio = 1.0 / $ratio;
}
return $ratio;
}
diff --git a/src/Module/AudioVideo/Nsv.php b/src/Module/AudioVideo/Nsv.php
index 8bc91960..1ccd4c1a 100644
--- a/src/Module/AudioVideo/Nsv.php
+++ b/src/Module/AudioVideo/Nsv.php
@@ -215,7 +215,7 @@ public function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) {
}
$info['playtime_seconds'] = $info['nsv']['NSVf']['playtime_ms'] / 1000;
- $info['bitrate'] = ($info['nsv']['NSVf']['file_size'] * 8) / $info['playtime_seconds'];
+ $info['bitrate'] = Utils::SafeDiv($info['nsv']['NSVf']['file_size'] * 8, $info['playtime_seconds']);
return true;
}
diff --git a/src/Module/AudioVideo/QuickTime.php b/src/Module/AudioVideo/QuickTime.php
index e730dc9e..d4742772 100644
--- a/src/Module/AudioVideo/QuickTime.php
+++ b/src/Module/AudioVideo/QuickTime.php
@@ -153,7 +153,7 @@ public function Analyze() {
} elseif (strlen($lat_deg) == 4) { // [+-]DDMM.M
$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60);
} elseif (strlen($lat_deg) == 6) { // [+-]DDMMSS.S
- $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
+ $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval((int) ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
}
if (strlen($lon_deg) == 3) { // [+-]DDD.D
@@ -161,7 +161,7 @@ public function Analyze() {
} elseif (strlen($lon_deg) == 5) { // [+-]DDDMM.M
$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60);
} elseif (strlen($lon_deg) == 7) { // [+-]DDDMMSS.S
- $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
+ $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval((int) ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
}
if (strlen($alt_deg) == 3) { // [+-]DDD.D
@@ -169,7 +169,7 @@ public function Analyze() {
} elseif (strlen($alt_deg) == 5) { // [+-]DDDMM.M
$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60);
} elseif (strlen($alt_deg) == 7) { // [+-]DDDMMSS.S
- $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
+ $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval((int) ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
}
foreach (array('latitude', 'longitude', 'altitude') as $key) {
@@ -333,7 +333,7 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
}
} elseif (isset($value_array['time_to_sample_table'])) {
foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
- if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) {
+ if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0) && !empty($info['quicktime']['time_scale'])) {
$framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
$framecount = $value_array2['sample_count'];
}
@@ -777,8 +777,8 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
case 'stsd': // Sample Table Sample Description atom
- $atom_structure['version'] = Utils::BigEndian2Int(substr($atom_data, 0, 1));
- $atom_structure['flags_raw'] = Utils::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
+ $atom_structure['version'] = Utils::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00
+ $atom_structure['flags_raw'] = Utils::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000
$atom_structure['number_entries'] = Utils::BigEndian2Int(substr($atom_data, 4, 4));
// see: https://github.com/JamesHeinrich/getID3/issues/111
@@ -806,7 +806,6 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
$stsdEntriesDataOffset += 2;
$atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
$stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
-
if (substr($atom_structure['sample_description_table'][$i]['data'], 1, 54) == 'application/octet-stream;type=com.parrot.videometadata') {
// special handling for apparently-malformed (TextMetaDataSampleEntry?) data for some version of Parrot drones
$atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['mime_type'] = substr($atom_structure['sample_description_table'][$i]['data'], 1, 55);
@@ -894,7 +893,8 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
break;
case 'mp4a':
- default:
+ $atom_structure['sample_description_table'][$i]['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_structure['sample_description_table'][$i]['data'], 20), $baseoffset + $stsdEntriesDataOffset - 20 - 16, $atomHierarchy, $ParseAllPossibleAtoms);
+
$info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
$info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
$info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels'];
@@ -920,6 +920,9 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
break;
}
break;
+
+ default:
+ break;
}
break;
@@ -1665,7 +1668,7 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
);
$atom_structure['data'] = $atom_data;
$atom_structure['image_mime'] = 'image/jpeg';
- $atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
+ $atom_structure['description'] = $descriptions[$atomname];
$info['quicktime']['comments']['picture'][] = array(
'image_mime' => $atom_structure['image_mime'],
'data' => $atom_data,
@@ -1681,7 +1684,7 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
case 'NCHD': // Nikon:MakerNoteVersion - https://exiftool.org/TagNames/Nikon.html
$makerNoteVersion = '';
for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
- if (ord($atom_data[$i]) >= 0x00 && ord($atom_data[$i]) <= 0x1F) {
+ if (ord($atom_data[$i]) <= 0x1F) {
$makerNoteVersion .= ' '.ord($atom_data[$i]);
} else {
$makerNoteVersion .= $atom_data[$i];
@@ -2099,6 +2102,97 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
break;
+ case 'esds': // Elementary Stream DeScriptor
+ // https://github.com/JamesHeinrich/getID3/issues/414
+ // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.cc
+ // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.h
+ $atom_structure['version'] = Utils::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00
+ $atom_structure['flags_raw'] = Utils::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000
+ $esds_offset = 4;
+
+ $atom_structure['ES_DescrTag'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ if ($atom_structure['ES_DescrTag'] != 0x03) {
+ $this->warning('expecting esds.ES_DescrTag = 0x03, found 0x'.Utils::PrintHexBytes($atom_structure['ES_DescrTag']).'), at offset '.$atom_structure['offset']);
+ break;
+ }
+ $atom_structure['ES_DescrSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+ $atom_structure['ES_ID'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+ $esds_offset += 2;
+ $atom_structure['ES_flagsraw'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ $atom_structure['ES_flags']['stream_dependency'] = (bool) ($atom_structure['ES_flagsraw'] & 0x80);
+ $atom_structure['ES_flags']['url_flag'] = (bool) ($atom_structure['ES_flagsraw'] & 0x40);
+ $atom_structure['ES_flags']['ocr_stream'] = (bool) ($atom_structure['ES_flagsraw'] & 0x20);
+ $atom_structure['ES_stream_priority'] = ($atom_structure['ES_flagsraw'] & 0x1F);
+ if ($atom_structure['ES_flags']['url_flag']) {
+ $this->warning('Unsupported esds.url_flag enabled at offset '.$atom_structure['offset']);
+ break;
+ }
+ if ($atom_structure['ES_flags']['stream_dependency']) {
+ $atom_structure['ES_dependsOn_ES_ID'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+ $esds_offset += 2;
+ }
+ if ($atom_structure['ES_flags']['ocr_stream']) {
+ $atom_structure['ES_OCR_ES_Id'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+ $esds_offset += 2;
+ }
+
+ $atom_structure['ES_DecoderConfigDescrTag'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ if ($atom_structure['ES_DecoderConfigDescrTag'] != 0x04) {
+ $this->warning('expecting esds.ES_DecoderConfigDescrTag = 0x04, found 0x'.Utils::PrintHexBytes($atom_structure['ES_DecoderConfigDescrTag']).'), at offset '.$atom_structure['offset']);
+ break;
+ }
+ $atom_structure['ES_DecoderConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+ $atom_structure['ES_objectTypeIndication'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ // https://stackoverflow.com/questions/3987850
+ // 0x40 = "Audio ISO/IEC 14496-3" = MPEG-4 Audio
+ // 0x67 = "Audio ISO/IEC 13818-7 LowComplexity Profile" = MPEG-2 AAC LC
+ // 0x69 = "Audio ISO/IEC 13818-3" = MPEG-2 Backward Compatible Audio (MPEG-2 Layers 1, 2, and 3)
+ // 0x6B = "Audio ISO/IEC 11172-3" = MPEG-1 Audio (MPEG-1 Layers 1, 2, and 3)
+
+ $streamTypePlusFlags = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ $atom_structure['ES_streamType'] = ($streamTypePlusFlags & 0xFC) >> 2;
+ $atom_structure['ES_upStream'] = (bool) ($streamTypePlusFlags & 0x02) >> 1;
+ $atom_structure['ES_bufferSizeDB'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 3));
+ $esds_offset += 3;
+ $atom_structure['ES_maxBitrate'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 4));
+ $esds_offset += 4;
+ $atom_structure['ES_avgBitrate'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 4));
+ $esds_offset += 4;
+ if ($atom_structure['ES_avgBitrate']) {
+ $info['quicktime']['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
+ $info['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
+ }
+
+ $atom_structure['ES_DecSpecificInfoTag'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ if ($atom_structure['ES_DecSpecificInfoTag'] != 0x05) {
+ $this->warning('expecting esds.ES_DecSpecificInfoTag = 0x05, found 0x'.Utils::PrintHexBytes($atom_structure['ES_DecSpecificInfoTag']).'), at offset '.$atom_structure['offset']);
+ break;
+ }
+ $atom_structure['ES_DecSpecificInfoTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+ $atom_structure['ES_DecSpecificInfo'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_DecSpecificInfoTagSize']));
+ $esds_offset += $atom_structure['ES_DecSpecificInfoTagSize'];
+
+ $atom_structure['ES_SLConfigDescrTag'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+ $esds_offset += 1;
+ if ($atom_structure['ES_SLConfigDescrTag'] != 0x06) {
+ $this->warning('expecting esds.ES_SLConfigDescrTag = 0x05, found 0x'.Utils::PrintHexBytes($atom_structure['ES_SLConfigDescrTag']).'), at offset '.$atom_structure['offset']);
+ break;
+ }
+ $atom_structure['ES_SLConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+ $atom_structure['ES_SLConfigDescr'] = Utils::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_SLConfigDescrTagSize']));
+ $esds_offset += $atom_structure['ES_SLConfigDescrTagSize'];
+ break;
+
// AVIF-related - https://docs.rs/avif-parse/0.13.2/src/avif_parse/boxes.rs.html
case 'pitm': // Primary ITeM
case 'iloc': // Item LOCation
@@ -2989,6 +3083,7 @@ public function quicktime_time_to_sample_table($info) {
return array();
}
+
/**
* @param array $info
*
diff --git a/src/Module/AudioVideo/Real.php b/src/Module/AudioVideo/Real.php
index 39dde105..88f8f447 100644
--- a/src/Module/AudioVideo/Real.php
+++ b/src/Module/AudioVideo/Real.php
@@ -46,8 +46,13 @@ public function Analyze() {
$info['audio']['bits_per_sample'] = $info['real']['old_ra_header']['bits_per_sample'];
$info['audio']['channels'] = $info['real']['old_ra_header']['channels'];
- $info['playtime_seconds'] = 60 * ($info['real']['old_ra_header']['audio_bytes'] / $info['real']['old_ra_header']['bytes_per_minute']);
- $info['audio']['bitrate'] = 8 * ($info['real']['old_ra_header']['audio_bytes'] / $info['playtime_seconds']);
+ if ($info['real']['old_ra_header']['bytes_per_minute']) {
+ $info['playtime_seconds'] = 60 * ($info['real']['old_ra_header']['audio_bytes'] / $info['real']['old_ra_header']['bytes_per_minute']);
+ $info['audio']['bitrate'] = 8 * ($info['real']['old_ra_header']['audio_bytes'] / $info['playtime_seconds']);
+ } else {
+ $info['playtime_seconds'] = 0;
+ $info['audio']['bitrate'] = 0;
+ }
$info['audio']['codec'] = $this->RealAudioCodecFourCClookup($info['real']['old_ra_header']['fourcc'], $info['audio']['bitrate']);
foreach ($info['real']['old_ra_header']['comments'] as $key => $valuearray) {
diff --git a/src/Module/AudioVideo/Riff.php b/src/Module/AudioVideo/Riff.php
index 541b00fb..34c7108f 100644
--- a/src/Module/AudioVideo/Riff.php
+++ b/src/Module/AudioVideo/Riff.php
@@ -214,7 +214,7 @@ public function Analyze() {
$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV)
- $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']);
+ $info['playtime_seconds'] = (float)Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $thisfile_audio['bitrate']);
}
$thisfile_audio['lossless'] = false;
@@ -440,11 +440,11 @@ public function Analyze() {
$thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML;
if (isset($parsedXML['SPEED']['MASTER_SPEED'])) {
@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']);
- $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000);
+ $thisfile_riff_WAVE['iXML'][0]['master_speed'] = (int) $numerator / ($denominator ? $denominator : 1000);
}
if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) {
@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']);
- $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000);
+ $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = (int) $numerator / ($denominator ? $denominator : 1000);
}
if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) {
$samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0'));
@@ -521,7 +521,7 @@ public function Analyze() {
if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) {
$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
- $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']);
+ $info['playtime_seconds'] = (float)Utils::SafeDiv((($info['avdataend'] - $info['avdataoffset']) * 8), $thisfile_audio['bitrate']);
}
if (!empty($info['wavpack'])) {
@@ -531,7 +531,7 @@ public function Analyze() {
// Reset to the way it was - RIFF parsing will have messed this up
$info['avdataend'] = $Original['avdataend'];
- $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
+ $thisfile_audio['bitrate'] = Utils::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);
$this->fseek($info['avdataoffset'] - 44);
$RIFFdata = $this->fread(44);
@@ -632,7 +632,7 @@ public function Analyze() {
}
}
if ($info['avdataend'] > $info['filesize']) {
- switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') {
+ switch ($thisfile_audio_dataformat) {
case 'wavpack': // WavPack
case 'lpac': // LPAC
case 'ofr': // OptimFROG
@@ -672,7 +672,7 @@ public function Analyze() {
$this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
}
}
- if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) {
+ if ($thisfile_audio_dataformat == 'ac3') {
unset($thisfile_audio['bits_per_sample']);
if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) {
$thisfile_audio['bitrate'] = $info['ac3']['bitrate'];
@@ -781,15 +781,15 @@ public function Analyze() {
/** @var array $thisfile_riff_video_current */
$thisfile_riff_video_current = &$thisfile_riff_video[$streamindex];
- if ($thisfile_riff_raw_avih['dwWidth'] > 0) {
+ if ($thisfile_riff_raw_avih['dwWidth'] > 0) { // @phpstan-ignore-line
$thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth'];
$thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width'];
}
- if ($thisfile_riff_raw_avih['dwHeight'] > 0) {
+ if ($thisfile_riff_raw_avih['dwHeight'] > 0) { // @phpstan-ignore-line
$thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight'];
$thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height'];
}
- if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) {
+ if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { // @phpstan-ignore-line
$thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames'];
$thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames'];
}
@@ -1908,7 +1908,7 @@ public function ParseRIFF($startoffset, $maxoffset) {
if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) {
unset($RIFFchunk[$chunkname][$thisindex]);
}
- if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) {
+ if (count($RIFFchunk[$chunkname]) === 0) {
unset($RIFFchunk[$chunkname]);
}
$RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize);
@@ -2029,7 +2029,7 @@ public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) {
foreach ($RIFFinfoKeyLookup as $key => $value) {
if (isset($RIFFinfoArray[$key])) {
foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) {
- if (trim($commentdata['data']) != '') {
+ if (!empty($commentdata['data']) && trim($commentdata['data']) != '') {
if (isset($CommentsTargetArray[$value])) {
$CommentsTargetArray[$value][] = trim($commentdata['data']);
} else {
diff --git a/src/Module/Graphic/Bmp.php b/src/Module/Graphic/Bmp.php
index 38e4c26b..28a1524e 100644
--- a/src/Module/Graphic/Bmp.php
+++ b/src/Module/Graphic/Bmp.php
@@ -336,307 +336,311 @@ public function Analyze() {
}
if ($this->ExtractData) {
- $this->fseek($thisfile_bmp_header_raw['data_offset']);
- $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
- $BMPpixelData = $this->fread($thisfile_bmp_header_raw['height'] * $RowByteLength);
- $pixeldataoffset = 0;
- $thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : '');
- switch ($thisfile_bmp_header_raw['compression']) {
-
- case 0: // BI_RGB
- switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
- case 1:
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
- $paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
- for ($i = 7; $i >= 0; $i--) {
- $paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
- $col++;
+ if (!$thisfile_bmp_header_raw['width'] || !$thisfile_bmp_header_raw['height']) {
+ $thisfile_bmp['data'] = array();
+ } else {
+ $this->fseek($thisfile_bmp_header_raw['data_offset']);
+ $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
+ $BMPpixelData = $this->fread($thisfile_bmp_header_raw['height'] * $RowByteLength);
+ $pixeldataoffset = 0;
+ $thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : '');
+ switch ($thisfile_bmp_header_raw['compression']) {
+
+ case 0: // BI_RGB
+ switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
+ case 1:
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
+ $paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
+ for ($i = 7; $i >= 0; $i--) {
+ $paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
+ $col++;
+ }
+ }
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
}
}
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
+ break;
+
+ case 4:
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
+ $paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
+ for ($i = 1; $i >= 0; $i--) {
+ $paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
+ $col++;
+ }
+ }
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
+ }
}
- }
- break;
-
- case 4:
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
- $paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
- for ($i = 1; $i >= 0; $i--) {
- $paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
+ break;
+
+ case 8:
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
+ $paletteindex = ord($BMPpixelData[$pixeldataoffset++]);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
- $col++;
+ }
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
}
}
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
- }
- }
- break;
-
- case 8:
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
- $paletteindex = ord($BMPpixelData[$pixeldataoffset++]);
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
- }
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
- }
- }
- break;
-
- case 24:
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
- $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
- $pixeldataoffset += 3;
- }
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
- }
- }
- break;
-
- case 32:
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
- $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+3]) << 24) | (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
- $pixeldataoffset += 4;
- }
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
- }
- }
- break;
+ break;
- case 16:
- // ?
- break;
-
- default:
- $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
- break;
- }
- break;
-
-
- case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
- switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
- case 8:
- $pixelcounter = 0;
- while ($pixeldataoffset < strlen($BMPpixelData)) {
- $firstbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $secondbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- if ($firstbyte == 0) {
-
- // escaped/absolute mode - the first byte of the pair can be set to zero to
- // indicate an escape character that denotes the end of a line, the end of
- // a bitmap, or a delta, depending on the value of the second byte.
- switch ($secondbyte) {
- case 0:
- // end of line
- // no need for special processing, just ignore
- break;
-
- case 1:
- // end of bitmap
- $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
- break;
-
- case 2:
- // delta - The 2 bytes following the escape contain unsigned values
- // indicating the horizontal and vertical offsets of the next pixel
- // from the current position.
- $colincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $rowincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
- $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
- $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
- break;
-
- default:
- // In absolute mode, the first byte is zero and the second byte is a
- // value in the range 03H through FFH. The second byte represents the
- // number of bytes that follow, each of which contains the color index
- // of a single pixel. Each run must be aligned on a word boundary.
- for ($i = 0; $i < $secondbyte; $i++) {
- $paletteindex = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
- $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
- $pixelcounter++;
- }
- while (($pixeldataoffset % 2) != 0) {
- // Each run must be aligned on a word boundary.
- $pixeldataoffset++;
- }
- break;
+ case 24:
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
+ $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
+ $pixeldataoffset += 3;
}
-
- } else {
-
- // encoded mode - the first byte specifies the number of consecutive pixels
- // to be drawn using the color index contained in the second byte.
- for ($i = 0; $i < $firstbyte; $i++) {
- $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
- $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
- $pixelcounter++;
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
}
-
}
- }
- break;
+ break;
- default:
- $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
- break;
- }
- break;
-
-
-
- case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
- switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
- case 4:
- $pixelcounter = 0;
- $paletteindexes = array();
- while ($pixeldataoffset < strlen($BMPpixelData)) {
- $firstbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $secondbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- if ($firstbyte == 0) {
-
- // escaped/absolute mode - the first byte of the pair can be set to zero to
- // indicate an escape character that denotes the end of a line, the end of
- // a bitmap, or a delta, depending on the value of the second byte.
- switch ($secondbyte) {
- case 0:
- // end of line
- // no need for special processing, just ignore
- break;
-
- case 1:
- // end of bitmap
- $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
- break;
-
- case 2:
- // delta - The 2 bytes following the escape contain unsigned values
- // indicating the horizontal and vertical offsets of the next pixel
- // from the current position.
- $colincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $rowincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
- $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
- $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
- break;
-
- default:
- // In absolute mode, the first byte is zero. The second byte contains the number
- // of color indexes that follow. Subsequent bytes contain color indexes in their
- // high- and low-order 4 bits, one color index for each pixel. In absolute mode,
- // each run must be aligned on a word boundary.
- $paletteindexes = array();
- for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
- $paletteindexbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
- $paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
- $paletteindexes[] = ($paletteindexbyte & 0x0F);
- }
- while (($pixeldataoffset % 2) != 0) {
- // Each run must be aligned on a word boundary.
- $pixeldataoffset++;
- }
-
- foreach ($paletteindexes as $paletteindex) {
- $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
- $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
- $pixelcounter++;
- }
- break;
+ case 32:
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
+ $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+3]) << 24) | (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
+ $pixeldataoffset += 4;
}
-
- } else {
-
- // encoded mode - the first byte of the pair contains the number of pixels to be
- // drawn using the color indexes in the second byte. The second byte contains two
- // color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
- // The first of the pixels is drawn using the color specified by the high-order
- // 4 bits, the second is drawn using the color in the low-order 4 bits, the third
- // is drawn using the color in the high-order 4 bits, and so on, until all the
- // pixels specified by the first byte have been drawn.
- $paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
- $paletteindexes[1] = ($secondbyte & 0x0F);
- for ($i = 0; $i < $firstbyte; $i++) {
- $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
- $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
- $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
- $pixelcounter++;
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
}
+ }
+ break;
+
+ case 16:
+ // ?
+ break;
+
+ default:
+ $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
+ break;
+ }
+ break;
+
+
+ case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
+ switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
+ case 8:
+ $pixelcounter = 0;
+ while ($pixeldataoffset < strlen($BMPpixelData)) {
+ $firstbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $secondbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ if ($firstbyte == 0) {
+
+ // escaped/absolute mode - the first byte of the pair can be set to zero to
+ // indicate an escape character that denotes the end of a line, the end of
+ // a bitmap, or a delta, depending on the value of the second byte.
+ switch ($secondbyte) {
+ case 0:
+ // end of line
+ // no need for special processing, just ignore
+ break;
+
+ case 1:
+ // end of bitmap
+ $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
+ break;
+
+ case 2:
+ // delta - The 2 bytes following the escape contain unsigned values
+ // indicating the horizontal and vertical offsets of the next pixel
+ // from the current position.
+ $colincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $rowincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
+ $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
+ $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
+ break;
+
+ default:
+ // In absolute mode, the first byte is zero and the second byte is a
+ // value in the range 03H through FFH. The second byte represents the
+ // number of bytes that follow, each of which contains the color index
+ // of a single pixel. Each run must be aligned on a word boundary.
+ for ($i = 0; $i < $secondbyte; $i++) {
+ $paletteindex = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
+ $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
+ $pixelcounter++;
+ }
+ while (($pixeldataoffset % 2) != 0) {
+ // Each run must be aligned on a word boundary.
+ $pixeldataoffset++;
+ }
+ break;
+ }
+
+ } else {
+
+ // encoded mode - the first byte specifies the number of consecutive pixels
+ // to be drawn using the color index contained in the second byte.
+ for ($i = 0; $i < $firstbyte; $i++) {
+ $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
+ $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
+ $pixelcounter++;
+ }
+ }
}
- }
- break;
+ break;
+
+ default:
+ $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
+ break;
+ }
+ break;
+
+
+
+ case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
+ switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
+ case 4:
+ $pixelcounter = 0;
+ $paletteindexes = array();
+ while ($pixeldataoffset < strlen($BMPpixelData)) {
+ $firstbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $secondbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ if ($firstbyte == 0) {
+
+ // escaped/absolute mode - the first byte of the pair can be set to zero to
+ // indicate an escape character that denotes the end of a line, the end of
+ // a bitmap, or a delta, depending on the value of the second byte.
+ switch ($secondbyte) {
+ case 0:
+ // end of line
+ // no need for special processing, just ignore
+ break;
+
+ case 1:
+ // end of bitmap
+ $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
+ break;
+
+ case 2:
+ // delta - The 2 bytes following the escape contain unsigned values
+ // indicating the horizontal and vertical offsets of the next pixel
+ // from the current position.
+ $colincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $rowincrement = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
+ $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
+ $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
+ break;
+
+ default:
+ // In absolute mode, the first byte is zero. The second byte contains the number
+ // of color indexes that follow. Subsequent bytes contain color indexes in their
+ // high- and low-order 4 bits, one color index for each pixel. In absolute mode,
+ // each run must be aligned on a word boundary.
+ $paletteindexes = array();
+ for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
+ $paletteindexbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
+ $paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
+ $paletteindexes[] = ($paletteindexbyte & 0x0F);
+ }
+ while (($pixeldataoffset % 2) != 0) {
+ // Each run must be aligned on a word boundary.
+ $pixeldataoffset++;
+ }
+
+ foreach ($paletteindexes as $paletteindex) {
+ $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
+ $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
+ $pixelcounter++;
+ }
+ break;
+ }
+
+ } else {
+
+ // encoded mode - the first byte of the pair contains the number of pixels to be
+ // drawn using the color indexes in the second byte. The second byte contains two
+ // color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
+ // The first of the pixels is drawn using the color specified by the high-order
+ // 4 bits, the second is drawn using the color in the low-order 4 bits, the third
+ // is drawn using the color in the high-order 4 bits, and so on, until all the
+ // pixels specified by the first byte have been drawn.
+ $paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
+ $paletteindexes[1] = ($secondbyte & 0x0F);
+ for ($i = 0; $i < $firstbyte; $i++) {
+ $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
+ $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
+ $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
+ $pixelcounter++;
+ }
- default:
- $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
- break;
- }
- break;
-
-
- case 3: // BI_BITFIELDS
- switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
- case 16:
- case 32:
- $redshift = 0;
- $greenshift = 0;
- $blueshift = 0;
- while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
- $redshift++;
- }
- while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
- $greenshift++;
- }
- while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
- $blueshift++;
- }
- for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
- for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
- $pixelvalue = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
- $pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
-
- $red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
- $green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
- $blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
- $thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
+ }
+ }
+ break;
+
+ default:
+ $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
+ break;
+ }
+ break;
+
+
+ case 3: // BI_BITFIELDS
+ switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
+ case 16:
+ case 32:
+ $redshift = 0;
+ $greenshift = 0;
+ $blueshift = 0;
+ while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
+ $redshift++;
+ }
+ while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
+ $greenshift++;
+ }
+ while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
+ $blueshift++;
}
- while (($pixeldataoffset % 4) != 0) {
- // lines are padded to nearest DWORD
- $pixeldataoffset++;
+ for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
+ for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
+ $pixelvalue = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
+ $pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
+
+ $red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
+ $green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
+ $blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
+ $thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
+ }
+ while (($pixeldataoffset % 4) != 0) {
+ // lines are padded to nearest DWORD
+ $pixeldataoffset++;
+ }
}
- }
- break;
+ break;
- default:
- $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
- break;
- }
- break;
+ default:
+ $this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
+ break;
+ }
+ break;
- default: // unhandled compression type
- $this->error('Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data');
- break;
+ default: // unhandled compression type
+ $this->error('Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data');
+ break;
+ }
}
}
diff --git a/src/Module/Graphic/Jpg.php b/src/Module/Graphic/Jpg.php
index b18cb29d..ec0619c6 100644
--- a/src/Module/Graphic/Jpg.php
+++ b/src/Module/Graphic/Jpg.php
@@ -184,7 +184,7 @@ public function Analyze() {
* @return mixed
*/
public function CastAsAppropriate($value) {
- if (is_array($value)) {
+ if (is_array($value) || is_null($value)) {
return $value;
} elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) {
return Utils::DecimalizeFraction($value);
diff --git a/src/Module/Handler.php b/src/Module/Handler.php
index d95a94fc..cf10fc1f 100644
--- a/src/Module/Handler.php
+++ b/src/Module/Handler.php
@@ -109,6 +109,8 @@ public function setStringMode($string) {
}
/**
+ * @phpstan-impure
+ *
* @return int|bool
*/
protected function ftell() {
@@ -121,6 +123,8 @@ protected function ftell() {
/**
* @param int $bytes
*
+ * @phpstan-impure
+ *
* @return string|false
*
* @throws Exception
@@ -166,6 +170,8 @@ protected function fread($bytes) {
* @param int $bytes
* @param int $whence
*
+ * @phpstan-impure
+ *
* @return int
*
* @throws Exception
@@ -207,6 +213,8 @@ protected function fseek($bytes, $whence=SEEK_SET) {
}
/**
+ * @phpstan-impure
+ *
* @return string|false
*
* @throws Exception
@@ -262,6 +270,8 @@ protected function fgets() {
}
/**
+ * @phpstan-impure
+ *
* @return bool
*/
protected function feof() {
diff --git a/src/Module/Tag/ApeTag.php b/src/Module/Tag/ApeTag.php
index 337390dd..7c339a1f 100644
--- a/src/Module/Tag/ApeTag.php
+++ b/src/Module/Tag/ApeTag.php
@@ -267,7 +267,7 @@ public function Analyze() {
case 'cover art (publisher logo)':
case 'cover art (recording)':
case 'cover art (studio)':
- // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
+ // list of possible cover arts from https://github.com/mono/taglib-sharp/blob/taglib-sharp-2.0.3.2/src/TagLib/Ape/Tag.cs
if (is_array($thisfile_ape_items_current['data'])) {
$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
@@ -332,7 +332,7 @@ public function Analyze() {
$info['ape']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
- } while (false);
+ } while (false); // @phpstan-ignore-line
break;
default:
diff --git a/src/Module/Tag/ID3v1.php b/src/Module/Tag/ID3v1.php
index c370e7cf..9b9c6aea 100644
--- a/src/Module/Tag/ID3v1.php
+++ b/src/Module/Tag/ID3v1.php
@@ -66,7 +66,7 @@ public function Analyze() {
if (!empty($ParsedID3v1['genre'])) {
unset($ParsedID3v1['genreid']);
}
- if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
+ if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) {
unset($ParsedID3v1['genre']);
}
diff --git a/src/Module/Tag/ID3v2.php b/src/Module/Tag/ID3v2.php
index a9300347..36306ec1 100644
--- a/src/Module/Tag/ID3v2.php
+++ b/src/Module/Tag/ID3v2.php
@@ -1493,7 +1493,7 @@ public function ParseID3v2Frame(&$parsedFrame) {
unset($comments_picture_data);
}
}
- } while (false);
+ } while (false); // @phpstan-ignore-line
}
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
@@ -3752,18 +3752,12 @@ public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
* @return bool
*/
public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
- for ($i = 0; $i < strlen($numberstring); $i++) {
- if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
- if (($numberstring[$i] == '.') && $allowdecimal) {
- // allowed
- } elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
- // allowed
- } else {
- return false;
- }
- }
- }
- return true;
+ $pattern = '#^';
+ $pattern .= ($allownegative ? '\\-?' : '');
+ $pattern .= '[0-9]+';
+ $pattern .= ($allowdecimal ? '(\\.[0-9]+)?' : '');
+ $pattern .= '$#';
+ return preg_match($pattern, $numberstring);
}
/**
@@ -3772,10 +3766,7 @@ public static function IsANumber($numberstring, $allowdecimal=false, $allownegat
* @return bool
*/
public static function IsValidDateStampString($datestamp) {
- if (strlen($datestamp) != 8) {
- return false;
- }
- if (!self::IsANumber($datestamp, false)) {
+ if (!preg_match('#^[12][0-9]{3}[01][0-9][0123][0-9]$#', $datestamp)) {
return false;
}
$year = substr($datestamp, 0, 4);
diff --git a/src/Module/Tag/Xmp.php b/src/Module/Tag/Xmp.php
index 03c8f0dd..acb58299 100644
--- a/src/Module/Tag/Xmp.php
+++ b/src/Module/Tag/Xmp.php
@@ -34,6 +34,77 @@
*************************************************************************************************/
class Xmp
{
+ /**
+ * The names of the JPEG segment markers, indexed by their marker number
+ */
+ private static $jpeg_segments_name = array(
+ 0x01 => 'TEM',
+ 0x02 => 'RES',
+ 0xC0 => 'SOF0',
+ 0xC1 => 'SOF1',
+ 0xC2 => 'SOF2',
+ 0xC3 => 'SOF4',
+ 0xC4 => 'DHT',
+ 0xC5 => 'SOF5',
+ 0xC6 => 'SOF6',
+ 0xC7 => 'SOF7',
+ 0xC8 => 'JPG',
+ 0xC9 => 'SOF9',
+ 0xCA => 'SOF10',
+ 0xCB => 'SOF11',
+ 0xCC => 'DAC',
+ 0xCD => 'SOF13',
+ 0xCE => 'SOF14',
+ 0xCF => 'SOF15',
+ 0xD0 => 'RST0',
+ 0xD1 => 'RST1',
+ 0xD2 => 'RST2',
+ 0xD3 => 'RST3',
+ 0xD4 => 'RST4',
+ 0xD5 => 'RST5',
+ 0xD6 => 'RST6',
+ 0xD7 => 'RST7',
+ 0xD8 => 'SOI',
+ 0xD9 => 'EOI',
+ 0xDA => 'SOS',
+ 0xDB => 'DQT',
+ 0xDC => 'DNL',
+ 0xDD => 'DRI',
+ 0xDE => 'DHP',
+ 0xDF => 'EXP',
+ 0xE0 => 'APP0',
+ 0xE1 => 'APP1',
+ 0xE2 => 'APP2',
+ 0xE3 => 'APP3',
+ 0xE4 => 'APP4',
+ 0xE5 => 'APP5',
+ 0xE6 => 'APP6',
+ 0xE7 => 'APP7',
+ 0xE8 => 'APP8',
+ 0xE9 => 'APP9',
+ 0xEA => 'APP10',
+ 0xEB => 'APP11',
+ 0xEC => 'APP12',
+ 0xED => 'APP13',
+ 0xEE => 'APP14',
+ 0xEF => 'APP15',
+ 0xF0 => 'JPG0',
+ 0xF1 => 'JPG1',
+ 0xF2 => 'JPG2',
+ 0xF3 => 'JPG3',
+ 0xF4 => 'JPG4',
+ 0xF5 => 'JPG5',
+ 0xF6 => 'JPG6',
+ 0xF7 => 'JPG7',
+ 0xF8 => 'JPG8',
+ 0xF9 => 'JPG9',
+ 0xFA => 'JPG10',
+ 0xFB => 'JPG11',
+ 0xFC => 'JPG12',
+ 0xFD => 'JPG13',
+ 0xFE => 'COM',
+ );
+
/**
* @var string
* The name of the image file that contains the XMP fields to extract and modify.
@@ -147,12 +218,21 @@ public function _get_jpeg_header_data($filename)
$segdatastart = ftell($filehnd);
// Read the segment data with length indicated by the previously read size
- $segdata = fread($filehnd, $decodedsize['size'] - 2);
+ // fread will complain about trying to read zero bytes: "fread(): Argument #2 ($length) must be greater than 0" -- https://github.com/JamesHeinrich/getID3/issues/418
+ if ($decodedsize['size'] > 2) {
+ $segdata = fread($filehnd, $decodedsize['size'] - 2);
+ } elseif ($decodedsize['size'] == 2) {
+ $segdata = '';
+ } else {
+ // invalid length
+ fclose($filehnd);
+ return false;
+ }
// Store the segment information in the output array
$headerdata[] = array(
'SegType' => ord($data[1]),
- 'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data[1])],
+ 'SegName' => self::$jpeg_segments_name[ord($data[1])],
'SegDataStart' => $segdatastart,
'SegData' => $segdata,
);
@@ -702,77 +782,4 @@ public function __construct($sFilename)
'exif:Rows',
'exif:Settings',
);
-*/
-
-/**
-* Global Variable: JPEG_Segment_Names
-*
-* The names of the JPEG segment markers, indexed by their marker number
-*/
-$GLOBALS['JPEG_Segment_Names'] = array(
- 0x01 => 'TEM',
- 0x02 => 'RES',
- 0xC0 => 'SOF0',
- 0xC1 => 'SOF1',
- 0xC2 => 'SOF2',
- 0xC3 => 'SOF4',
- 0xC4 => 'DHT',
- 0xC5 => 'SOF5',
- 0xC6 => 'SOF6',
- 0xC7 => 'SOF7',
- 0xC8 => 'JPG',
- 0xC9 => 'SOF9',
- 0xCA => 'SOF10',
- 0xCB => 'SOF11',
- 0xCC => 'DAC',
- 0xCD => 'SOF13',
- 0xCE => 'SOF14',
- 0xCF => 'SOF15',
- 0xD0 => 'RST0',
- 0xD1 => 'RST1',
- 0xD2 => 'RST2',
- 0xD3 => 'RST3',
- 0xD4 => 'RST4',
- 0xD5 => 'RST5',
- 0xD6 => 'RST6',
- 0xD7 => 'RST7',
- 0xD8 => 'SOI',
- 0xD9 => 'EOI',
- 0xDA => 'SOS',
- 0xDB => 'DQT',
- 0xDC => 'DNL',
- 0xDD => 'DRI',
- 0xDE => 'DHP',
- 0xDF => 'EXP',
- 0xE0 => 'APP0',
- 0xE1 => 'APP1',
- 0xE2 => 'APP2',
- 0xE3 => 'APP3',
- 0xE4 => 'APP4',
- 0xE5 => 'APP5',
- 0xE6 => 'APP6',
- 0xE7 => 'APP7',
- 0xE8 => 'APP8',
- 0xE9 => 'APP9',
- 0xEA => 'APP10',
- 0xEB => 'APP11',
- 0xEC => 'APP12',
- 0xED => 'APP13',
- 0xEE => 'APP14',
- 0xEF => 'APP15',
- 0xF0 => 'JPG0',
- 0xF1 => 'JPG1',
- 0xF2 => 'JPG2',
- 0xF3 => 'JPG3',
- 0xF4 => 'JPG4',
- 0xF5 => 'JPG5',
- 0xF6 => 'JPG6',
- 0xF7 => 'JPG7',
- 0xF8 => 'JPG8',
- 0xF9 => 'JPG9',
- 0xFA => 'JPG10',
- 0xFB => 'JPG11',
- 0xFC => 'JPG12',
- 0xFD => 'JPG13',
- 0xFE => 'COM',
-);
+*/
\ No newline at end of file
diff --git a/src/Utils.php b/src/Utils.php
index 3bee5b86..8f2dae44 100644
--- a/src/Utils.php
+++ b/src/Utils.php
@@ -277,6 +277,18 @@ public static function intValueSupported($num)
return false;
}
+ /**
+ * Perform a division, guarding against division by zero
+ *
+ * @param float|int $numerator
+ * @param float|int $denominator
+ * @param float|int $fallback
+ * @return float|int
+ */
+ public static function SafeDiv($numerator, $denominator, $fallback = 0) {
+ return $denominator ? $numerator / $denominator : $fallback;
+ }
+
/**
* @param string $fraction
*
@@ -284,7 +296,7 @@ public static function intValueSupported($num)
*/
public static function DecimalizeFraction($fraction) {
list($numerator, $denominator) = explode('/', $fraction);
- return $numerator / ($denominator ? $denominator : 1);
+ return (int) $numerator / ($denominator ? $denominator : 1);
}
/**
@@ -1021,10 +1033,6 @@ public static function iconv_fallback_int_utf8($charval) {
* @return string
*/
public static function iconv_fallback_iso88591_utf8($string, $bom=false) {
- if (function_exists('utf8_encode')) {
- return utf8_encode($string);
- }
- // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
$newcharstring = '';
if ($bom) {
$newcharstring .= "\xEF\xBB\xBF";
@@ -1093,10 +1101,6 @@ public static function iconv_fallback_iso88591_utf16($string) {
* @return string
*/
public static function iconv_fallback_utf8_iso88591($string) {
- if (function_exists('utf8_decode')) {
- return utf8_decode($string);
- }
- // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
$newcharstring = '';
$offset = 0;
$stringlength = strlen($string);
diff --git a/src/Write/ID3v1.php b/src/Write/ID3v1.php
index 8db6a52c..f9a399c8 100644
--- a/src/Write/ID3v1.php
+++ b/src/Write/ID3v1.php
@@ -77,7 +77,8 @@ public function WriteID3v1() {
(isset($this->tag_data['year'] ) ? $this->tag_data['year'] : ''),
(isset($this->tag_data['genreid'] ) ? $this->tag_data['genreid'] : ''),
(isset($this->tag_data['comment'] ) ? $this->tag_data['comment'] : ''),
- (isset($this->tag_data['track_number']) ? $this->tag_data['track_number'] : ''));
+ $this->tag_data['track_number']
+ );
fwrite($fp_source, $new_id3v1_tag_data, 128);
fclose($fp_source);
return true;
diff --git a/src/Write/ID3v2.php b/src/Write/ID3v2.php
index 03cfdf88..7bf46329 100644
--- a/src/Write/ID3v2.php
+++ b/src/Write/ID3v2.php
@@ -123,26 +123,12 @@ public function WriteID3v2() {
if (file_exists($this->filename) && Utils::isWritable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag))) {
// best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary)
- if (file_exists($this->filename)) {
-
- if (is_readable($this->filename) && Utils::isWritable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'r+b'))) {
- rewind($fp);
- fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
- fclose($fp);
- } else {
- $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
- }
-
+ if (is_readable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'r+b'))) {
+ rewind($fp);
+ fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
+ fclose($fp);
} else {
-
- if (Utils::isWritable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'wb'))) {
- rewind($fp);
- fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
- fclose($fp);
- } else {
- $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")';
- }
-
+ $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
}
} else {
@@ -221,7 +207,7 @@ public function RemoveID3v2() {
if ($OldThisFileInfo['avdataoffset'] !== false) {
fseek($fp_source, $OldThisFileInfo['avdataoffset']);
}
- if (Utils::isWritable($this->filename) && is_file($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) {
+ if (Utils::isWritable($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) {
while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
fwrite($fp_temp, $buffer, strlen($buffer));
}
@@ -261,7 +247,7 @@ public function RemoveID3v2() {
fwrite($fp_temp, $buffer, strlen($buffer));
}
fclose($fp_source);
- if (Utils::isWritable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'wb'))) {
+ if (Utils::isWritable($this->filename) && ($fp_source = fopen($this->filename, 'wb'))) {
rewind($fp_temp);
while ($buffer = fread($fp_temp, $this->fread_buffer_size)) {
fwrite($fp_source, $buffer, strlen($buffer));
diff --git a/src/WriteTags.php b/src/WriteTags.php
index 03d4677d..4c91c56d 100644
--- a/src/WriteTags.php
+++ b/src/WriteTags.php
@@ -661,7 +661,7 @@ public function FormatDataForID3v2($id3v2_majorversion) {
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
$ID3v2_tag_data_converted = true;
- } while (false);
+ } while (false); // @phpstan-ignore-line
}
if (!$ID3v2_tag_data_converted) {
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1;
|