diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 0c67be1b..cbbf0072 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -6,7 +6,6 @@ jobs:
lint:
name: "Lint"
runs-on: "ubuntu-latest"
- continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
@@ -20,21 +19,15 @@ jobs:
- "7.3"
- "7.4"
- "8.0"
- experimental:
- - false
- include:
- - php-version: "8.1"
- experimental: true
- composer-options: "--ignore-platform-reqs"
+ - "8.1"
steps:
- uses: "actions/checkout@v2"
- uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
+ ini-values: error_reporting=-1, display_errors=On
coverage: "none"
- uses: "ramsey/composer-install@v1"
- with:
- composer-options: "${{ matrix.composer-options }}"
- name: "Run the linter"
run: "composer lint -- --colors"
diff --git a/changelog.txt b/changelog.txt
index 476313da..5b8ffa5a 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -18,6 +18,42 @@
Version History
===============
+1.9.21: [2021-09-22] James Heinrich :: 1.9.21-202109171300
+ » add support for RIFF.guan
+ ¤ add ID3v1 genres 148-191
+ ¤ torrent files easy access key
+ * bugfix #342 demo.mysqli.php XSS
+ * bugfix #340 default quicktime.ReturnAtomData=false
+ * bugfix #338 improved transliterated tag merging
+ * bugfix #337 PHP 8.1 compatibility
+ * bugfix #335 PHP 8.1 compatibility
+ * bugfix #330 QuicktimeContentRatingLookup 'rtng'
+ * bugfix #328 throw exception if a resource seek fails
+ * bugfix #326 improved temporary path detection
+ * bugfix #325 INF/NAN constants instead of float/string
+ * bugfix #324 Nikon-specific atoms in QuickTime
+ * bugfix #321 prevent errors on corrupt JPEGs
+ * bugfix #319 prevent error in ZIP contents MIME detect
+ * bugfix #315 ID3v2 USLT check for data length
+ * bugfix #308 silence libxml deprecation warning
+ * bugfix #304 undefined index: comments
+ * bugfix #299 decbin type error in PHP8
+ * bugfix #298 error scanning WAV via file pointer
+ * bugfix #294 replace IMG_JPG with IMAGETYPE_JPEG
+ * bugfix #292 PDFs take long time to parse
+ * bugfix #291 divzero QuickTime with no playable content
+ * bugfix #290 detect ID3v1 on minimal example files
+ * bugfix #289 avoid crash on invalid TIFF
+ * bugfix #287 mp3 CBR detected as VBR
+ * bugfix #286 corrupt mp3 can cause slow scanning
+ * bugfix #284 allow "0" as a value in tags
+ * bugfix #283 array offset on value of type int
+ * bugfix #277 ID3v2 add new Turkish Lira TRY
+ * bugfix #270 demo.mysqli.php LONGBLOB
+ * bugfix #266 fix possible endless loop on PNG
+ * bugfix #257 undefined variables
+ * bugfix #207 improved LAME version string parsing
+
1.9.20: [2020-06-30] James Heinrich :: 1.9.20-202006061653
» add support for DSDIFF audio
» add support for TAK lossess audio
diff --git a/demos/demo.mp3header.php b/demos/demo.mp3header.php
index 96bf7ac9..222fe40e 100644
--- a/demos/demo.mp3header.php
+++ b/demos/demo.mp3header.php
@@ -474,8 +474,8 @@ function Dec2Bin($number) {
}
$bytes[] = $number;
$binstring = '';
- for ($i = 0; $i < count($bytes); $i++) {
- $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
+ foreach ($bytes as $i => $byte) {
+ $binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
}
return $binstring;
}
@@ -586,8 +586,8 @@ function is_hash($var) {
if (is_array($var)) {
$keys = array_keys($var);
$all_num = true;
- for ($i = 0; $i < count($keys); $i++) {
- if (is_string($keys[$i])) {
+ foreach ($keys as $key) {
+ if (is_string($key)) {
return true;
}
}
diff --git a/demos/demo.mysqli.php b/demos/demo.mysqli.php
index 9b32f632..e7471b97 100644
--- a/demos/demo.mysqli.php
+++ b/demos/demo.mysqli.php
@@ -1495,7 +1495,7 @@ function SynchronizeAllTags($filename, $synchronizefrom='all', $synchronizeto='A
} else {
echo 'Show all Encoder Options
';
- echo 'Files with Encoder Options '.$_REQUEST['showtagfiles'].':
';
+ echo 'Files with Encoder Options '.htmlentities($_REQUEST['showtagfiles']).':
';
echo '';
while ($row = mysqli_fetch_array($result)) {
echo '';
diff --git a/src/Cache/Dbm.php b/src/Cache/Dbm.php
index 030fcf0c..c84598a8 100644
--- a/src/Cache/Dbm.php
+++ b/src/Cache/Dbm.php
@@ -226,6 +226,7 @@ public function clear_cache() {
*/
public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
+ $key = null;
if (file_exists($filename)) {
// Calc key filename::mod_time::size - should be unique
diff --git a/src/GetID3.php b/src/GetID3.php
index 26aab980..bc41d1ff 100644
--- a/src/GetID3.php
+++ b/src/GetID3.php
@@ -226,7 +226,7 @@ class GetID3
*
* @var bool
*/
- public $options_audiovideo_quicktime_ReturnAtomData = true;
+ public $options_audiovideo_quicktime_ReturnAtomData = false;
/** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
@@ -319,7 +319,7 @@ class GetID3
*/
protected $startup_warning = '';
- const VERSION = '2.0.x-202105131611';
+ const VERSION = '2.0.x-202109171300';
const FREAD_BUFFER_SIZE = 32768;
const ATTACHMENTS_NONE = false;
diff --git a/src/Module/Archive/Gzip.php b/src/Module/Archive/Gzip.php
index c94f92fd..ed19dc1f 100644
--- a/src/Module/Archive/Gzip.php
+++ b/src/Module/Archive/Gzip.php
@@ -59,7 +59,7 @@ public function Analyze() {
$num_members = 0;
while (true) {
$is_wrong_members = false;
- $num_members = intval(count($arr_members));
+ $num_members = count($arr_members);
for ($i = 0; $i < $num_members; $i++) {
if (strlen($arr_members[$i]) == 0) {
continue;
@@ -84,13 +84,13 @@ public function Analyze() {
$fpointer = 0;
$idx = 0;
- for ($i = 0; $i < $num_members; $i++) {
- if (strlen($arr_members[$i]) == 0) {
+ foreach ($arr_members as $member) {
+ if (strlen($member) == 0) {
continue;
}
$thisInfo = &$info['gzip']['member_header'][++$idx];
- $buff = "\x1F\x8B\x08".$arr_members[$i];
+ $buff = "\x1F\x8B\x08". $member;
$attr = unpack($unpack_header, substr($buff, 0, $start_length));
$thisInfo['filemtime'] = Utils::LittleEndian2Int($attr['mtime']);
diff --git a/src/Module/Archive/Tar.php b/src/Module/Archive/Tar.php
index 3b22c514..b702be1d 100644
--- a/src/Module/Archive/Tar.php
+++ b/src/Module/Archive/Tar.php
@@ -141,6 +141,9 @@ public function display_perms($mode) {
else $type='u'; // UNKNOWN
// Determine permissions
+ $owner = array();
+ $group = array();
+ $world = array();
$owner['read'] = (($mode & 00400) ? 'r' : '-');
$owner['write'] = (($mode & 00200) ? 'w' : '-');
$owner['execute'] = (($mode & 00100) ? 'x' : '-');
diff --git a/src/Module/Archive/Zip.php b/src/Module/Archive/Zip.php
index 0d99df72..6457e069 100644
--- a/src/Module/Archive/Zip.php
+++ b/src/Module/Archive/Zip.php
@@ -121,11 +121,11 @@ public function Analyze() {
!empty($info['zip']['files']['docProps']['core.xml'])) {
// http://technet.microsoft.com/en-us/library/cc179224.aspx
$info['fileformat'] = 'zip.msoffice';
- if (!empty($ThisFileInfo['zip']['files']['ppt'])) {
+ if (!empty($info['zip']['files']['ppt'])) {
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
- } elseif (!empty($ThisFileInfo['zip']['files']['xl'])) {
+ } elseif (!empty($info['zip']['files']['xl'])) {
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
- } elseif (!empty($ThisFileInfo['zip']['files']['word'])) {
+ } elseif (!empty($info['zip']['files']['word'])) {
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
}
@@ -231,6 +231,7 @@ public function getZIPentriesFilepointer() {
* @return array|false
*/
public function ZIPparseLocalFileHeader() {
+ $LocalFileHeader = array();
$LocalFileHeader['offset'] = $this->ftell();
$ZIPlocalFileHeader = $this->fread(30);
@@ -329,6 +330,7 @@ public function ZIPparseLocalFileHeader() {
* @return array|false
*/
public function ZIPparseCentralDirectory() {
+ $CentralDirectory = array();
$CentralDirectory['offset'] = $this->ftell();
$ZIPcentralDirectory = $this->fread(46);
@@ -388,6 +390,7 @@ public function ZIPparseCentralDirectory() {
* @return array|false
*/
public function ZIPparseEndOfCentralDirectory() {
+ $EndOfCentralDirectory = array();
$EndOfCentralDirectory['offset'] = $this->ftell();
$ZIPendOfCentralDirectory = $this->fread(22);
diff --git a/src/Module/Audio/Dsdiff.php b/src/Module/Audio/Dsdiff.php
index fbc28c73..6c37db2e 100644
--- a/src/Module/Audio/Dsdiff.php
+++ b/src/Module/Audio/Dsdiff.php
@@ -49,6 +49,7 @@ public function Analyze() {
$info['audio']['bits_per_sample'] = 1;
$info['dsdiff'] = array();
+ $thisChunk = null;
while (!$this->feof() && ($ChunkHeader = $this->fread(12))) {
if (strlen($ChunkHeader) < 12) {
$this->error('Expecting chunk header at offset '.(isset($thisChunk['offset']) ? $thisChunk['offset'] : 'N/A').', found insufficient data in file, aborting parsing');
diff --git a/src/Module/Audio/Flac.php b/src/Module/Audio/Flac.php
index 94934e35..ebc17c54 100644
--- a/src/Module/Audio/Flac.php
+++ b/src/Module/Audio/Flac.php
@@ -401,6 +401,7 @@ private function parseCUESHEET($BlockData) {
public function parsePICTURE() {
$info = &$this->getid3->info;
+ $picture = array();
$picture['typeid'] = Utils::BigEndian2Int($this->fread(4));
$picture['picturetype'] = self::pictureTypeLookup($picture['typeid']);
$picture['image_mime'] = $this->fread(Utils::BigEndian2Int($this->fread(4)));
diff --git a/src/Module/Audio/Lpac.php b/src/Module/Audio/Lpac.php
index e6e2e5d5..f2392e50 100644
--- a/src/Module/Audio/Lpac.php
+++ b/src/Module/Audio/Lpac.php
@@ -35,6 +35,7 @@ public function Analyze() {
$this->error('Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"');
return false;
}
+ $flags = array();
$info['avdataoffset'] += 14;
$info['fileformat'] = 'lpac';
diff --git a/src/Module/Audio/Midi.php b/src/Module/Audio/Midi.php
index f7a131f5..17b5756e 100644
--- a/src/Module/Audio/Midi.php
+++ b/src/Module/Audio/Midi.php
@@ -70,6 +70,7 @@ public function Analyze() {
$thisfile_midi_raw['ticksperqnote'] = Utils::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
+ $trackdataarray = array();
for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
while ((strlen($MIDIdata) - $offset) < 8) {
if ($buffer = $this->fread($this->getid3->fread_buffer_size())) {
@@ -94,7 +95,7 @@ public function Analyze() {
}
}
- if (!isset($trackdataarray) || !is_array($trackdataarray)) {
+ if (!is_array($trackdataarray) || count($trackdataarray) === 0) {
$this->error('Cannot find MIDI track information');
unset($thisfile_midi);
unset($info['fileformat']);
diff --git a/src/Module/Audio/Mp3.php b/src/Module/Audio/Mp3.php
index b8b80123..d9fbac2d 100644
--- a/src/Module/Audio/Mp3.php
+++ b/src/Module/Audio/Mp3.php
@@ -58,6 +58,7 @@ public function Analyze() {
$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
}
+ $CurrentDataLAMEversionString = null;
if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
$synchoffsetwarning = 'Unknown data before synch ';
@@ -124,6 +125,12 @@ public function Analyze() {
if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
$PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
+ if (!empty($info['audio']['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version']) && ($info['audio']['encoder'] == $info['mpeg']['audio']['LAME']['short_version'])) {
+ if (preg_match('#^LAME[0-9\\.]+#', $PossiblyLongerLAMEversion_NewString, $matches)) {
+ // "LAME3.100" -> "LAME3.100.1", but avoid including "(alpha)" and similar
+ $info['mpeg']['audio']['LAME']['short_version'] = $matches[0];
+ }
+ }
$info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
}
}
@@ -725,197 +732,200 @@ public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $S
$thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
$thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
- $thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
- if (preg_match('#^LAME([0-9\\.a-z]+)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
+
+ //$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
+ $thisfile_mpeg_audio_lame['numeric_version'] = '';
+ if (preg_match('#^LAME([0-9\\.a-z]*)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
$thisfile_mpeg_audio_lame['short_version'] = $matches[0];
$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
}
- foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
- $thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
- }
-
- //if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
- if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
-
- // extra 11 chars are not part of version string when LAMEtag present
- unset($thisfile_mpeg_audio_lame['long_version']);
-
- // It the LAME tag was only introduced in LAME v3.90
- // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
-
- // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
- // are assuming a 'Xing' identifier offset of 0x24, which is the case for
- // MPEG-1 non-mono, but not for other combinations
- $LAMEtagOffsetContant = $VBRidOffset - 0x24;
-
- // shortcuts
- $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array());
- $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD'];
- $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
- $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
- $thisfile_mpeg_audio_lame['raw'] = array();
- $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw'];
-
- // byte $9B VBR Quality
- // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
- // Actually overwrites original Xing bytes
- unset($thisfile_mpeg_audio['VBR_scale']);
- $thisfile_mpeg_audio_lame['vbr_quality'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
-
- // bytes $9C-$A4 Encoder short VersionString
- $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
-
- // byte $A5 Info Tag revision + VBR method
- $LAMEtagRevisionVBRmethod = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
-
- $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
- $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
- $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
- $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
-
- // byte $A6 Lowpass filter value
- $thisfile_mpeg_audio_lame['lowpass_frequency'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
-
- // bytes $A7-$AE Replay Gain
- // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
- // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
- if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
- // LAME 3.94a16 and later - 9.23 fixed point
- // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
- $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
- } else {
- // LAME 3.94a15 and earlier - 32-bit floating point
- // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
- $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = Utils::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
- }
- if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
- unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
- } else {
- $thisfile_mpeg_audio_lame_RGAD['peak_db'] = Utils::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+ if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) {
+ foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
+ $thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
}
+ //if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
+ if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
+
+ // extra 11 chars are not part of version string when LAMEtag present
+ unset($thisfile_mpeg_audio_lame['long_version']);
+
+ // It the LAME tag was only introduced in LAME v3.90
+ // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
+
+ // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
+ // are assuming a 'Xing' identifier offset of 0x24, which is the case for
+ // MPEG-1 non-mono, but not for other combinations
+ $LAMEtagOffsetContant = $VBRidOffset - 0x24;
+
+ // shortcuts
+ $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array());
+ $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD'];
+ $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
+ $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
+ $thisfile_mpeg_audio_lame['raw'] = array();
+ $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw'];
+
+ // byte $9B VBR Quality
+ // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
+ // Actually overwrites original Xing bytes
+ unset($thisfile_mpeg_audio['VBR_scale']);
+ $thisfile_mpeg_audio_lame['vbr_quality'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
+
+ // bytes $9C-$A4 Encoder short VersionString
+ $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
+
+ // byte $A5 Info Tag revision + VBR method
+ $LAMEtagRevisionVBRmethod = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
+
+ $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
+ $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
+ $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
+ $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
+
+ // byte $A6 Lowpass filter value
+ $thisfile_mpeg_audio_lame['lowpass_frequency'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
+
+ // bytes $A7-$AE Replay Gain
+ // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
+ // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
+ if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
+ // LAME 3.94a16 and later - 9.23 fixed point
+ // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
+ $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
+ } else {
+ // LAME 3.94a15 and earlier - 32-bit floating point
+ // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
+ $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = Utils::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
+ }
+ if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
+ unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+ } else {
+ $thisfile_mpeg_audio_lame_RGAD['peak_db'] = Utils::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+ }
- $thisfile_mpeg_audio_lame_raw['RGAD_track'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
- $thisfile_mpeg_audio_lame_raw['RGAD_album'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
+ $thisfile_mpeg_audio_lame_raw['RGAD_track'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
+ $thisfile_mpeg_audio_lame_raw['RGAD_album'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
- if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
+ if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
- $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
- $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
- $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
- $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
- $thisfile_mpeg_audio_lame_RGAD_track['name'] = Utils::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
- $thisfile_mpeg_audio_lame_RGAD_track['originator'] = Utils::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
- $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = Utils::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
+ $thisfile_mpeg_audio_lame_RGAD_track['name'] = Utils::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
+ $thisfile_mpeg_audio_lame_RGAD_track['originator'] = Utils::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
+ $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = Utils::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
- if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
- $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+ if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
+ $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+ }
+ $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
+ $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
+ } else {
+ unset($thisfile_mpeg_audio_lame_RGAD['track']);
}
- $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
- $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
- } else {
- unset($thisfile_mpeg_audio_lame_RGAD['track']);
- }
- if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
-
- $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
- $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
- $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
- $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
- $thisfile_mpeg_audio_lame_RGAD_album['name'] = Utils::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
- $thisfile_mpeg_audio_lame_RGAD_album['originator'] = Utils::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
- $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = Utils::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
-
- if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
- $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+ if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
+
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
+ $thisfile_mpeg_audio_lame_RGAD_album['name'] = Utils::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
+ $thisfile_mpeg_audio_lame_RGAD_album['originator'] = Utils::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
+ $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = Utils::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
+
+ if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
+ $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+ }
+ $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
+ $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
+ } else {
+ unset($thisfile_mpeg_audio_lame_RGAD['album']);
+ }
+ if (empty($thisfile_mpeg_audio_lame_RGAD)) {
+ unset($thisfile_mpeg_audio_lame['RGAD']);
}
- $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
- $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
- } else {
- unset($thisfile_mpeg_audio_lame_RGAD['album']);
- }
- if (empty($thisfile_mpeg_audio_lame_RGAD)) {
- unset($thisfile_mpeg_audio_lame['RGAD']);
- }
- // byte $AF Encoding flags + ATH Type
- $EncodingFlagsATHtype = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
- $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
- $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
- $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
- $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
- $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F;
-
- // byte $B0 if ABR {specified bitrate} else {minimal bitrate}
- $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
- if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
- $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
- } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
- // ignore
- } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
- $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
- }
+ // byte $AF Encoding flags + ATH Type
+ $EncodingFlagsATHtype = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
+ $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
+ $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
+ $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
+ $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
+ $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F;
+
+ // byte $B0 if ABR {specified bitrate} else {minimal bitrate}
+ $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
+ if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
+ $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
+ } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
+ // ignore
+ } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
+ $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
+ }
- // bytes $B1-$B3 Encoder delays
- $EncoderDelays = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
- $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
- $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF;
-
- // byte $B4 Misc
- $MiscByte = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
- $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03);
- $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
- $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
- $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
- $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
- $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
- $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
- $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
-
- // byte $B5 MP3 Gain
- $thisfile_mpeg_audio_lame_raw['mp3_gain'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
- $thisfile_mpeg_audio_lame['mp3_gain_db'] = (Utils::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
- $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
-
- // bytes $B6-$B7 Preset and surround info
- $PresetSurroundBytes = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
- // Reserved = ($PresetSurroundBytes & 0xC000);
- $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
- $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
- $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
- $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
- if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
- $this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
- }
- if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
- // this may change if 3.90.4 ever comes out
- $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
- }
+ // bytes $B1-$B3 Encoder delays
+ $EncoderDelays = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
+ $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
+ $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF;
+
+ // byte $B4 Misc
+ $MiscByte = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
+ $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03);
+ $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
+ $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
+ $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
+ $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
+ $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
+ $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
+ $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
+
+ // byte $B5 MP3 Gain
+ $thisfile_mpeg_audio_lame_raw['mp3_gain'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
+ $thisfile_mpeg_audio_lame['mp3_gain_db'] = (Utils::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
+ $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
+
+ // bytes $B6-$B7 Preset and surround info
+ $PresetSurroundBytes = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
+ // Reserved = ($PresetSurroundBytes & 0xC000);
+ $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
+ $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
+ $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
+ $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
+ if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
+ $this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
+ }
+ if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
+ // this may change if 3.90.4 ever comes out
+ $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
+ }
- // bytes $B8-$BB MusicLength
- $thisfile_mpeg_audio_lame['audio_bytes'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
- $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
+ // bytes $B8-$BB MusicLength
+ $thisfile_mpeg_audio_lame['audio_bytes'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
+ $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
- // bytes $BC-$BD MusicCRC
- $thisfile_mpeg_audio_lame['music_crc'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
+ // bytes $BC-$BD MusicCRC
+ $thisfile_mpeg_audio_lame['music_crc'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
- // bytes $BE-$BF CRC-16 of Info Tag
- $thisfile_mpeg_audio_lame['lame_tag_crc'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
+ // bytes $BE-$BF CRC-16 of Info Tag
+ $thisfile_mpeg_audio_lame['lame_tag_crc'] = Utils::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
- // LAME CBR
- if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
+ // LAME CBR
+ if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
- $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
- $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
- $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
- //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
- // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
- //}
+ $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
+ $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
+ $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
+ //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
+ // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
+ //}
- }
+ }
+ }
}
}
@@ -1294,6 +1304,7 @@ public function getOnlyMPEGaudioInfoBruteForce() {
$LongMPEGbitrateLookup = array();
$LongMPEGpaddingLookup = array();
$LongMPEGfrequencyLookup = array();
+ $Distribution = array();
$Distribution['bitrate'] = array();
$Distribution['frequency'] = array();
$Distribution['layer'] = array();
@@ -1456,6 +1467,7 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
$SynchSeekOffset = 0;
$SyncSeekAttempts = 0;
$SyncSeekAttemptsMax = 1000;
+ $FirstFrameThisfileInfo = null;
while ($SynchSeekOffset < $sync_seek_buffer_size) {
if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
@@ -1598,6 +1610,7 @@ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
$pct_data_scanned = 0;
for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
$frames_scanned_this_segment = 0;
+ $scan_start_offset = array();
if ($this->ftell() >= $info['avdataend']) {
break;
}
@@ -1927,6 +1940,7 @@ public static function MPEGaudioHeaderDecode($Header4Bytes) {
return false;
}
+ $MPEGrawHeader = array();
$MPEGrawHeader['synch'] = (Utils::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
$MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB
$MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC
diff --git a/src/Module/Audio/Mpc.php b/src/Module/Audio/Mpc.php
index c408203d..6532f297 100644
--- a/src/Module/Audio/Mpc.php
+++ b/src/Module/Audio/Mpc.php
@@ -348,6 +348,7 @@ public function ParseMPCsv6() {
$info['avdataoffset'] += $thisfile_mpc_header['size'];
// Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :)
+ $HeaderDWORD = array();
$HeaderDWORD[0] = Utils::LittleEndian2Int(substr($MPCheaderData, 0, 4));
$HeaderDWORD[1] = Utils::LittleEndian2Int(substr($MPCheaderData, 4, 4));
diff --git a/src/Module/Audio/Ogg.php b/src/Module/Audio/Ogg.php
index 221d13a8..4606b089 100644
--- a/src/Module/Audio/Ogg.php
+++ b/src/Module/Audio/Ogg.php
@@ -528,6 +528,7 @@ public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo)
*/
public function ParseOggPageHeader() {
// http://xiph.org/ogg/vorbis/doc/framing.html
+ $oggheader = array();
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
$filedata = $this->fread($this->getid3->fread_buffer_size());
diff --git a/src/Module/Audio/OptimFrog.php b/src/Module/Audio/OptimFrog.php
index cd156ea8..32fa8b1f 100644
--- a/src/Module/Audio/OptimFrog.php
+++ b/src/Module/Audio/OptimFrog.php
@@ -189,6 +189,7 @@ public function ParseOptimFROGheader45() {
case 'COMP':
// unlike other block types, there CAN be multiple COMP blocks
+ $COMPdata = array();
$COMPdata['offset'] = $BlockOffset;
$COMPdata['size'] = $BlockSize;
diff --git a/src/Module/AudioVideo/Asf.php b/src/Module/AudioVideo/Asf.php
index 4196c46e..d952dea1 100644
--- a/src/Module/AudioVideo/Asf.php
+++ b/src/Module/AudioVideo/Asf.php
@@ -95,6 +95,7 @@ public function Analyze() {
$offset = 0;
$thisfile_asf_streambitratepropertiesobject = array();
$thisfile_asf_codeclistobject = array();
+ $StreamPropertiesObjectData = array();
for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) {
$NextObjectGUID = substr($ASFHeaderData, $offset, 16);
diff --git a/src/Module/AudioVideo/Flv.php b/src/Module/AudioVideo/Flv.php
index 7be25e2c..d09e4915 100644
--- a/src/Module/AudioVideo/Flv.php
+++ b/src/Module/AudioVideo/Flv.php
@@ -163,6 +163,7 @@ public function Analyze() {
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
$FLVvideoHeader = $this->fread(11);
+ $PictureSizeEnc = array();
if ($info['flv']['video']['videoCodec'] === self::VIDEO_H264) {
// this code block contributed by: moysevichØgmail*com
diff --git a/src/Module/AudioVideo/Mpeg.php b/src/Module/AudioVideo/Mpeg.php
index 0bac2674..7b92c796 100644
--- a/src/Module/AudioVideo/Mpeg.php
+++ b/src/Module/AudioVideo/Mpeg.php
@@ -546,6 +546,7 @@ public static function systemNonOverheadPercentage($VideoBitrate, $AudioBitrate)
$VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss)
+ $OverheadMultiplierByBitrate = array();
//OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps)
$OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
$OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
diff --git a/src/Module/AudioVideo/QuickTime.php b/src/Module/AudioVideo/QuickTime.php
index ed624f88..ed378215 100644
--- a/src/Module/AudioVideo/QuickTime.php
+++ b/src/Module/AudioVideo/QuickTime.php
@@ -7,6 +7,7 @@
use JamesHeinrich\GetID3\Module\Handler;
use JamesHeinrich\GetID3\Module\Tag\ID3v1;
use JamesHeinrich\GetID3\Module\Tag\ID3v2;
+use JamesHeinrich\GetID3\Module\Tag\NikonNCTG;
use JamesHeinrich\GetID3\Utils;
/////////////////////////////////////////////////////////////////
@@ -30,7 +31,7 @@ class QuickTime extends Handler
*
* @var bool
*/
- public $ReturnAtomData = true;
+ public $ReturnAtomData = false;
/** audio-video.quicktime
* return all parsed data from all atoms if true, otherwise just returned parsed metadata
@@ -752,11 +753,13 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
$atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
$atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag'];
- $ptv_lookup[0] = 'normal';
- $ptv_lookup[1] = 'double';
- $ptv_lookup[2] = 'half';
- $ptv_lookup[3] = 'full';
- $ptv_lookup[4] = 'current';
+ $ptv_lookup = array(
+ 0 => 'normal',
+ 1 => 'double',
+ 2 => 'half',
+ 3 => 'full',
+ 4 => 'current'
+ );
if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
} else {
@@ -1622,33 +1625,60 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
break;
case 'NCDT':
- // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
+ // https://exiftool.org/TagNames/Nikon.html
// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
break;
case 'NCTH': // Nikon Camera THumbnail image
case 'NCVW': // Nikon Camera preVieW image
- // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
+ case 'NCM1': // Nikon Camera preview iMage 1
+ case 'NCM2': // Nikon Camera preview iMage 2
+ // https://exiftool.org/TagNames/Nikon.html
if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
+ $descriptions = array(
+ 'NCTH' => 'Nikon Camera Thumbnail Image',
+ 'NCVW' => 'Nikon Camera Preview Image',
+ 'NCM1' => 'Nikon Camera Preview Image 1',
+ 'NCM2' => 'Nikon Camera Preview Image 2',
+ );
$atom_structure['data'] = $atom_data;
$atom_structure['image_mime'] = 'image/jpeg';
- $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image'));
- $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']);
+ $atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
+ $info['quicktime']['comments']['picture'][] = array(
+ 'image_mime' => $atom_structure['image_mime'],
+ 'data' => $atom_data,
+ 'description' => $atom_structure['description']
+ );
}
break;
- case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
- $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data);
+ case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
+ $nikonNCTG = new NikonNCTG($this->getid3);
+
+ $atom_structure['data'] = $nikonNCTG->parse($atom_data);
+ break;
+ 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) {
+ $makerNoteVersion .= ' '.ord($atom_data[$i]);
+ } else {
+ $makerNoteVersion .= $atom_data[$i];
+ }
+ }
+ $makerNoteVersion = rtrim($makerNoteVersion, "\x00");
+ $atom_structure['data'] = array(
+ 'MakerNoteVersion' => $makerNoteVersion
+ );
break;
- case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
- case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
- case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
+ case 'NCDB': // Nikon - https://exiftool.org/TagNames/Nikon.html
+ case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
$atom_structure['data'] = $atom_data;
break;
case "\x00\x00\x00\x00":
// some kind of metacontainer, may contain a big data dump such as:
// mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
- // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
+ // https://xhelmboyx.tripod.com/formats/qti-layout.txt
$atom_structure['version'] = Utils::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = Utils::BigEndian2Int(substr($atom_data, 1, 3));
@@ -1745,6 +1775,7 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
'unknown_data' => array(),
'debug_list' => '', // Used to debug variables stored as comma delimited strings
);
+ $debug_structure = array();
$debug_structure['debug_items'] = array();
// Can start loop here to decode all sensor data in 32 Byte chunks:
foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) {
@@ -2063,7 +2094,7 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
* @return array|false
*/
public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
- $atom_structure = false;
+ $atom_structure = array();
$subatomoffset = 0;
$subatomcounter = 0;
if ((strlen($atom_data) == 4) && (Utils::BigEndian2Int($atom_data) == 0x00000000)) {
@@ -2081,17 +2112,22 @@ public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHiera
$subatomoffset += 4;
continue;
}
- return $atom_structure;
+ break;
}
if (strlen($subatomdata) < ($subatomsize - 8)) {
// we don't have enough data to decode the subatom.
// this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large
// so we passed in the start of a following atom incorrectly?
- return $atom_structure;
+ break;
}
$atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
$subatomoffset += $subatomsize;
}
+
+ if (empty($atom_structure)) {
+ return false;
+ }
+
return $atom_structure;
}
@@ -2576,8 +2612,9 @@ public function QuicktimeContentRatingLookup($rtng) {
static $QuicktimeContentRatingLookup = array();
if (empty($QuicktimeContentRatingLookup)) {
$QuicktimeContentRatingLookup[0] = 'None';
+ $QuicktimeContentRatingLookup[1] = 'Explicit';
$QuicktimeContentRatingLookup[2] = 'Clean';
- $QuicktimeContentRatingLookup[4] = 'Explicit';
+ $QuicktimeContentRatingLookup[4] = 'Explicit (old)';
}
return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
}
@@ -2630,189 +2667,6 @@ public function QuicktimeStoreFrontCodeLookup($sfid) {
return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
}
- /**
- * @param string $atom_data
- *
- * @return array
- */
- public function QuicktimeParseNikonNCTG($atom_data) {
- // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
- // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
- // Data is stored as records of:
- // * 4 bytes record type
- // * 2 bytes size of data field type:
- // 0x0001 = flag (size field *= 1-byte)
- // 0x0002 = char (size field *= 1-byte)
- // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
- // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
- // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
- // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
- // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
- // * 2 bytes data size field
- // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
- // all integers are stored BigEndian
-
- $NCTGtagName = array(
- 0x00000001 => 'Make',
- 0x00000002 => 'Model',
- 0x00000003 => 'Software',
- 0x00000011 => 'CreateDate',
- 0x00000012 => 'DateTimeOriginal',
- 0x00000013 => 'FrameCount',
- 0x00000016 => 'FrameRate',
- 0x00000022 => 'FrameWidth',
- 0x00000023 => 'FrameHeight',
- 0x00000032 => 'AudioChannels',
- 0x00000033 => 'AudioBitsPerSample',
- 0x00000034 => 'AudioSampleRate',
- 0x02000001 => 'MakerNoteVersion',
- 0x02000005 => 'WhiteBalance',
- 0x0200000b => 'WhiteBalanceFineTune',
- 0x0200001e => 'ColorSpace',
- 0x02000023 => 'PictureControlData',
- 0x02000024 => 'WorldTime',
- 0x02000032 => 'UnknownInfo',
- 0x02000083 => 'LensType',
- 0x02000084 => 'Lens',
- );
-
- $offset = 0;
- $data = null;
- $datalength = strlen($atom_data);
- $parsed = array();
- while ($offset < $datalength) {
- $record_type = Utils::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4;
- $data_size_type = Utils::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
- $data_size = Utils::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
- switch ($data_size_type) {
- case 0x0001: // 0x0001 = flag (size field *= 1-byte)
- $data = Utils::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
- $offset += ($data_size * 1);
- break;
- case 0x0002: // 0x0002 = char (size field *= 1-byte)
- $data = substr($atom_data, $offset, $data_size * 1);
- $offset += ($data_size * 1);
- $data = rtrim($data, "\x00");
- break;
- case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
- $data = '';
- for ($i = $data_size - 1; $i >= 0; $i--) {
- $data .= substr($atom_data, $offset + ($i * 2), 2);
- }
- $data = Utils::BigEndian2Int($data);
- $offset += ($data_size * 2);
- break;
- case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
- $data = '';
- for ($i = $data_size - 1; $i >= 0; $i--) {
- $data .= substr($atom_data, $offset + ($i * 4), 4);
- }
- $data = Utils::BigEndian2Int($data);
- $offset += ($data_size * 4);
- break;
- case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
- $data = array();
- for ($i = 0; $i < $data_size; $i++) {
- $numerator = Utils::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
- $denomninator = Utils::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
- if ($denomninator == 0) {
- $data[$i] = false;
- } else {
- $data[$i] = (double) $numerator / $denomninator;
- }
- }
- $offset += (8 * $data_size);
- if (count($data) == 1) {
- $data = $data[0];
- }
- break;
- case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
- $data = substr($atom_data, $offset, $data_size * 1);
- $offset += ($data_size * 1);
- break;
- case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
- $data = substr($atom_data, $offset, $data_size * 2);
- $offset += ($data_size * 2);
- break;
- default:
- echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
';
- break 2;
- }
-
- switch ($record_type) {
- case 0x00000011: // CreateDate
- case 0x00000012: // DateTimeOriginal
- $data = strtotime($data);
- break;
- case 0x0200001e: // ColorSpace
- switch ($data) {
- case 1:
- $data = 'sRGB';
- break;
- case 2:
- $data = 'Adobe RGB';
- break;
- }
- break;
- case 0x02000023: // PictureControlData
- $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
- $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a');
- $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a');
- $data = array(
- 'PictureControlVersion' => substr($data, 0, 4),
- 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
- 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
- //'?' => substr($data, 44, 4),
- 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))],
- 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)),
- 'Sharpness' => ord(substr($data, 50, 1)),
- 'Contrast' => ord(substr($data, 51, 1)),
- 'Brightness' => ord(substr($data, 52, 1)),
- 'Saturation' => ord(substr($data, 53, 1)),
- 'HueAdjustment' => ord(substr($data, 54, 1)),
- 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))],
- 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))],
- 'ToningSaturation' => ord(substr($data, 57, 1)),
- );
- break;
- case 0x02000024: // WorldTime
- // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime
- // timezone is stored as offset from GMT in minutes
- $timezone = Utils::BigEndian2Int(substr($data, 0, 2));
- if ($timezone & 0x8000) {
- $timezone = 0 - (0x10000 - $timezone);
- }
- $timezone /= 60;
-
- $dst = (bool) Utils::BigEndian2Int(substr($data, 2, 1));
- switch (Utils::BigEndian2Int(substr($data, 3, 1))) {
- case 2:
- $datedisplayformat = 'D/M/Y'; break;
- case 1:
- $datedisplayformat = 'M/D/Y'; break;
- case 0:
- default:
- $datedisplayformat = 'Y/M/D'; break;
- }
-
- $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
- break;
- case 0x02000083: // LensType
- $data = array(
- //'_' => $data,
- 'mf' => (bool) ($data & 0x01),
- 'd' => (bool) ($data & 0x02),
- 'g' => (bool) ($data & 0x04),
- 'vr' => (bool) ($data & 0x08),
- );
- break;
- }
- $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
- $parsed[$tag_name] = $data;
- }
- return $parsed;
- }
-
/**
* @param string $keyname
* @param string|array $data
diff --git a/src/Module/AudioVideo/Riff.php b/src/Module/AudioVideo/Riff.php
index 92e28f88..3f47f103 100644
--- a/src/Module/AudioVideo/Riff.php
+++ b/src/Module/AudioVideo/Riff.php
@@ -56,6 +56,7 @@ public function Analyze() {
$thisfile_riff_video = &$thisfile_riff['video'];
$thisfile_riff_WAVE = array();
+ $Original = array();
$Original['avdataoffset'] = $info['avdataoffset'];
$Original['avdataend'] = $info['avdataend'];
@@ -316,6 +317,7 @@ public function Analyze() {
$thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601)));
if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) {
if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) {
+ $bext_timestamp = array();
list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date;
list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time;
$thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']);
@@ -797,6 +799,7 @@ public function Analyze() {
}
if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) {
if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) {
+ $thisfile_riff_raw_strf_strhfccType_streamindex = null;
for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) {
if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) {
$strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'];
@@ -1572,6 +1575,8 @@ public function ParseRIFF($startoffset, $maxoffset) {
$RIFFchunk = false;
$FoundAllChunksWeNeed = false;
+ $LISTchunkParent = null;
+ $LISTchunkMaxOffset = null;
$AC3syncwordBytes = pack('n', Ac3::syncword); // 0x0B77 -> "\x0B\x77"
try {
@@ -2139,6 +2144,7 @@ public function parseWavPackHeader($WavPackChunkData) {
*/
public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {
+ $parsed = array();
$parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure
$parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels
$parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner
diff --git a/src/Module/AudioVideo/Swf.php b/src/Module/AudioVideo/Swf.php
index 4ff9f759..6e541c82 100644
--- a/src/Module/AudioVideo/Swf.php
+++ b/src/Module/AudioVideo/Swf.php
@@ -120,7 +120,7 @@ public function Analyze() {
$CurrentOffset += 4;
}
- unset($TagData);
+ $TagData = array();
$TagData['offset'] = $CurrentOffset;
$TagData['size'] = $TagLength;
$TagData['id'] = $TagID;
diff --git a/src/Module/Graphic/Bmp.php b/src/Module/Graphic/Bmp.php
index db63cc02..38e4c26b 100644
--- a/src/Module/Graphic/Bmp.php
+++ b/src/Module/Graphic/Bmp.php
@@ -509,6 +509,7 @@ public function Analyze() {
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));
@@ -544,7 +545,6 @@ public function Analyze() {
// 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.
- unset($paletteindexes);
$paletteindexes = array();
for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
$paletteindexbyte = Utils::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
diff --git a/src/Module/Graphic/Pcd.php b/src/Module/Graphic/Pcd.php
index ea22dbb2..c08c8992 100644
--- a/src/Module/Graphic/Pcd.php
+++ b/src/Module/Graphic/Pcd.php
@@ -55,6 +55,7 @@ public function Analyze() {
} elseif ($this->ExtractData > 0) {
+ $PCD_levels = array();
$PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16
$PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4
$PCD_levels[3] = array( 768, 512, 0x30000); // BASE
diff --git a/src/Module/Graphic/Png.php b/src/Module/Graphic/Png.php
index fc1c4e7f..103f6a90 100644
--- a/src/Module/Graphic/Png.php
+++ b/src/Module/Graphic/Png.php
@@ -57,6 +57,7 @@ public function Analyze() {
}
while ((($this->ftell() - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) {
+ $chunk = array();
$chunk['data_length'] = Utils::BigEndian2Int(substr($PNGfiledata, $offset, 4));
if ($chunk['data_length'] === false) {
$this->error('Failed to read data_length at offset '.$offset);
diff --git a/src/Module/Handler.php b/src/Module/Handler.php
index 6d47998f..ed1bc5e6 100644
--- a/src/Module/Handler.php
+++ b/src/Module/Handler.php
@@ -180,19 +180,25 @@ protected function fseek($bytes, $whence=SEEK_SET) {
$this->data_string_position = $this->data_string_length + $bytes;
break;
}
- return 0;
- } else {
- $pos = $bytes;
- if ($whence == SEEK_CUR) {
- $pos = $this->ftell() + $bytes;
- } elseif ($whence == SEEK_END) {
- $pos = $this->getid3->info['filesize'] + $bytes;
- }
- if (!Utils::intValueSupported($pos)) {
- throw new Exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
- }
+ return 0; // fseek returns 0 on success
+ }
+
+ $pos = $bytes;
+ if ($whence == SEEK_CUR) {
+ $pos = $this->ftell() + $bytes;
+ } elseif ($whence == SEEK_END) {
+ $pos = $this->getid3->info['filesize'] + $bytes;
+ }
+ if (!Utils::intValueSupported($pos)) {
+ throw new Exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
+ }
+
+ // https://github.com/JamesHeinrich/getID3/issues/327
+ $result = fseek($this->getid3->fp, $bytes, $whence);
+ if ($result !== 0) { // fseek returns 0 on success
+ throw new Exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10);
}
- return fseek($this->getid3->fp, $bytes, $whence);
+ return $result;
}
/**
diff --git a/src/Module/Misc/Cue.php b/src/Module/Misc/Cue.php
index 3463c052..8b1c3d0a 100644
--- a/src/Module/Misc/Cue.php
+++ b/src/Module/Misc/Cue.php
@@ -91,10 +91,10 @@ public function parseCueSheet($file)
{
//-1 means still global, all others are track specific
$track_on = -1;
+ $currentFile = null;
- for ($i=0; $i < count($file); $i++)
- {
- list($key) = explode(' ', strtolower($file[$i]), 2);
+ foreach ($file as $line) {
+ list($key) = explode(' ', strtolower($line), 2);
switch ($key)
{
case 'catalog':
@@ -103,25 +103,25 @@ public function parseCueSheet($file)
case 'performer':
case 'songwriter':
case 'title':
- $this->parseString($file[$i], $track_on);
+ $this->parseString($line, $track_on);
break;
case 'file':
- $currentFile = $this->parseFile($file[$i]);
+ $currentFile = $this->parseFile($line);
break;
case 'flags':
- $this->parseFlags($file[$i], $track_on);
+ $this->parseFlags($line, $track_on);
break;
case 'index':
case 'postgap':
case 'pregap':
- $this->parseIndex($file[$i], $track_on);
+ $this->parseIndex($line, $track_on);
break;
case 'rem':
- $this->parseComment($file[$i], $track_on);
+ $this->parseComment($line, $track_on);
break;
case 'track':
$track_on++;
- $this->parseTrack($file[$i], $track_on);
+ $this->parseTrack($line, $track_on);
if (isset($currentFile)) // if there's a file
{
$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
@@ -129,7 +129,7 @@ public function parseCueSheet($file)
break;
default:
//save discarded junk and place string[] with track it was found in
- $this->parseGarbage($file[$i], $track_on);
+ $this->parseGarbage($line, $track_on);
break;
}
}
diff --git a/src/Module/Misc/Iso.php b/src/Module/Misc/Iso.php
index aeb4637b..d6a01838 100644
--- a/src/Module/Misc/Iso.php
+++ b/src/Module/Misc/Iso.php
@@ -252,6 +252,7 @@ public function ParsePathTable() {
$offset = 0;
$pathcounter = 1;
+ $FullPathArray = array();
while ($offset < $PathTableSize) {
// shortcut
$info['iso']['path_table']['directories'][$pathcounter] = array();
diff --git a/src/Module/Tag/ApeTag.php b/src/Module/Tag/ApeTag.php
index 9d763073..337390dd 100644
--- a/src/Module/Tag/ApeTag.php
+++ b/src/Module/Tag/ApeTag.php
@@ -360,6 +360,7 @@ public function parseAPEheaderFooter($APEheaderFooterData) {
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
// shortcut
+ $headerfooterinfo = array();
$headerfooterinfo['raw'] = array();
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
@@ -389,6 +390,7 @@ public function parseAPEtagFlags($rawflagint) {
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
// All are set to zero on creation and ignored on reading."
// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
+ $flags = array();
$flags['header'] = (bool) ($rawflagint & 0x80000000);
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
diff --git a/src/Module/Tag/ID3v1.php b/src/Module/Tag/ID3v1.php
index c50e50a2..c370e7cf 100644
--- a/src/Module/Tag/ID3v1.php
+++ b/src/Module/Tag/ID3v1.php
@@ -46,6 +46,7 @@ public function Analyze() {
$info['avdataend'] = $info['filesize'] - 128;
+ $ParsedID3v1 = array();
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
diff --git a/src/Module/Tag/ID3v2.php b/src/Module/Tag/ID3v2.php
index a71ea436..572f54e6 100644
--- a/src/Module/Tag/ID3v2.php
+++ b/src/Module/Tag/ID3v2.php
@@ -344,7 +344,7 @@ public function Analyze() {
}
if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
- unset($parsedFrame);
+ $parsedFrame = array();
$parsedFrame['frame_name'] = $frame_name;
$parsedFrame['frame_flags_raw'] = $frame_flags;
$parsedFrame['data'] = substr($framedata, 0, $frame_size);
@@ -1373,6 +1373,8 @@ public function ParseID3v2Frame(&$parsedFrame) {
$frame_textencoding_terminator = "\x00";
}
+ $frame_imagetype = null;
+ $frame_mimetype = null;
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
if (strtolower($frame_imagetype) == 'ima') {
@@ -1959,18 +1961,14 @@ public function ParseID3v2Frame(&$parsedFrame) {
$frame_offset = 0;
$parsedFrame['peakamplitude'] = Utils::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
- $rg_track_adjustment = Utils::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
- $frame_offset += 2;
- $rg_album_adjustment = Utils::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
- $frame_offset += 2;
- $parsedFrame['raw']['track']['name'] = Utils::Bin2Dec(substr($rg_track_adjustment, 0, 3));
- $parsedFrame['raw']['track']['originator'] = Utils::Bin2Dec(substr($rg_track_adjustment, 3, 3));
- $parsedFrame['raw']['track']['signbit'] = Utils::Bin2Dec(substr($rg_track_adjustment, 6, 1));
- $parsedFrame['raw']['track']['adjustment'] = Utils::Bin2Dec(substr($rg_track_adjustment, 7, 9));
- $parsedFrame['raw']['album']['name'] = Utils::Bin2Dec(substr($rg_album_adjustment, 0, 3));
- $parsedFrame['raw']['album']['originator'] = Utils::Bin2Dec(substr($rg_album_adjustment, 3, 3));
- $parsedFrame['raw']['album']['signbit'] = Utils::Bin2Dec(substr($rg_album_adjustment, 6, 1));
- $parsedFrame['raw']['album']['adjustment'] = Utils::Bin2Dec(substr($rg_album_adjustment, 7, 9));
+ foreach (array('track','album') as $rgad_entry_type) {
+ $rg_adjustment_word = Utils::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
+ $frame_offset += 2;
+ $parsedFrame['raw'][$rgad_entry_type]['name'] = ($rg_adjustment_word & 0xE000) >> 13;
+ $parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
+ $parsedFrame['raw'][$rgad_entry_type]['signbit'] = ($rg_adjustment_word & 0x0200) >> 9;
+ $parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
+ }
$parsedFrame['track']['name'] = Utils::RGADnameLookup($parsedFrame['raw']['track']['name']);
$parsedFrame['track']['originator'] = Utils::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
$parsedFrame['track']['adjustment'] = Utils::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
diff --git a/src/Module/Tag/Lyrics3.php b/src/Module/Tag/Lyrics3.php
index 14fc1489..79fe20e6 100644
--- a/src/Module/Tag/Lyrics3.php
+++ b/src/Module/Tag/Lyrics3.php
@@ -36,6 +36,9 @@ public function Analyze() {
}
$this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
+ $lyrics3offset = null;
+ $lyrics3version = null;
+ $lyrics3size = null;
$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
$lyrics3lsz = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
diff --git a/src/Module/Tag/NikonNCTG.php b/src/Module/Tag/NikonNCTG.php
new file mode 100644
index 00000000..dce8447a
--- /dev/null
+++ b/src/Module/Tag/NikonNCTG.php
@@ -0,0 +1,1452 @@
+ //
+// available at https://github.com/JamesHeinrich/getID3 //
+// or https://www.getid3.org //
+// or http://getid3.sourceforge.net //
+// see readme.txt for more details //
+/////////////////////////////////////////////////////////////////
+// //
+// module.tag.nikon-nctg.php //
+// //
+/////////////////////////////////////////////////////////////////
+use JamesHeinrich\GetID3\GetID3;
+use JamesHeinrich\GetID3\Utils;
+
+/**
+ * Module for analyzing Nikon NCTG metadata in MOV files
+ *
+ * @author Pavel Starosek
+ * @author Phil Harvey
+ *
+ * @link https://exiftool.org/TagNames/Nikon.html#NCTG
+ * @link https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/Nikon.pm
+ * @link https://leo-van-stee.github.io/
+ */
+class NikonNCTG
+{
+ const EXIF_TYPE_UINT8 = 0x0001;
+ const EXIF_TYPE_CHAR = 0x0002;
+ const EXIF_TYPE_UINT16 = 0x0003;
+ const EXIF_TYPE_UINT32 = 0x0004;
+ const EXIF_TYPE_URATIONAL = 0x0005;
+ const EXIF_TYPE_INT8 = 0x0006;
+ const EXIF_TYPE_RAW = 0x0007;
+ const EXIF_TYPE_INT16 = 0x0008;
+ const EXIF_TYPE_INT32 = 0x0009;
+ const EXIF_TYPE_RATIONAL = 0x000A;
+
+ protected static $exifTypeSizes = array(
+ self::EXIF_TYPE_UINT8 => 1,
+ self::EXIF_TYPE_CHAR => 1,
+ self::EXIF_TYPE_UINT16 => 2,
+ self::EXIF_TYPE_UINT32 => 4,
+ self::EXIF_TYPE_URATIONAL => 8,
+ self::EXIF_TYPE_INT8 => 1,
+ self::EXIF_TYPE_RAW => 1,
+ self::EXIF_TYPE_INT16 => 2,
+ self::EXIF_TYPE_INT32 => 4,
+ self::EXIF_TYPE_RATIONAL => 8,
+ );
+
+ protected static $exposurePrograms = array(
+ 0 => 'Not Defined',
+ 1 => 'Manual',
+ 2 => 'Program AE',
+ 3 => 'Aperture-priority AE',
+ 4 => 'Shutter speed priority AE',
+ 5 => 'Creative (Slow speed)',
+ 6 => 'Action (High speed)',
+ 7 => 'Portrait',
+ 8 => 'Landscape'
+ );
+
+ protected static $meteringModes = array(
+ 0 => 'Unknown',
+ 1 => 'Average',
+ 2 => 'Center-weighted average',
+ 3 => 'Spot',
+ 4 => 'Multi-spot',
+ 5 => 'Multi-segment',
+ 6 => 'Partial',
+ 255 => 'Other'
+ );
+
+ protected static $cropHiSpeeds = array(
+ 0 => 'Off',
+ 1 => '1.3x Crop',
+ 2 => 'DX Crop',
+ 3 => '5:4 Crop',
+ 4 => '3:2 Crop',
+ 6 => '16:9 Crop',
+ 8 => '2.7x Crop',
+ 9 => 'DX Movie Crop',
+ 10 => '1.3x Movie Crop',
+ 11 => 'FX Uncropped',
+ 12 => 'DX Uncropped',
+ 15 => '1.5x Movie Crop',
+ 17 => '1:1 Crop'
+ );
+
+ protected static $colorSpaces = array(
+ 1 => 'sRGB',
+ 2 => 'Adobe RGB'
+ );
+
+ protected static $vibrationReductions = array(
+ 1 => 'On',
+ 2 => 'Off'
+ );
+
+ protected static $VRModes = array(
+ 0 => 'Normal',
+ 1 => 'On (1)',
+ 2 => 'Active',
+ 3 => 'Sport'
+ );
+
+ protected static $activeDLightnings = array(
+ 0 => 'Off',
+ 1 => 'Low',
+ 3 => 'Normal',
+ 5 => 'High',
+ 7 => 'Extra High',
+ 8 => 'Extra High 1',
+ 9 => 'Extra High 2',
+ 10 => 'Extra High 3',
+ 11 => 'Extra High 4',
+ 65535 => 'Auto'
+ );
+
+ protected static $pictureControlDataAdjusts = array(
+ 0 => 'default',
+ 1 => 'quick',
+ 2 => 'full'
+ );
+
+ protected static $pictureControlDataFilterEffects = array(
+ 0x80 => 'off',
+ 0x81 => 'yellow',
+ 0x82 => 'orange',
+ 0x83 => 'red',
+ 0x84 => 'green',
+ 0xff => 'n/a'
+ );
+
+ protected static $pictureControlDataToningEffects = array(
+ 0x80 => 'b&w',
+ 0x81 => 'sepia',
+ 0x82 => 'cyanotype',
+ 0x83 => 'red',
+ 0x84 => 'yellow',
+ 0x85 => 'green',
+ 0x86 => 'blue-green',
+ 0x87 => 'blue',
+ 0x88 => 'purple-blue',
+ 0x89 => 'red-purple',
+ 0xff => 'n/a'
+ );
+
+ protected static $isoInfoExpansions = array(
+ 0x0000 => 'Off',
+ 0x0101 => 'Hi 0.3',
+ 0x0102 => 'Hi 0.5',
+ 0x0103 => 'Hi 0.7',
+ 0x0104 => 'Hi 1.0',
+ 0x0105 => 'Hi 1.3',
+ 0x0106 => 'Hi 1.5',
+ 0x0107 => 'Hi 1.7',
+ 0x0108 => 'Hi 2.0',
+ 0x0109 => 'Hi 2.3',
+ 0x010a => 'Hi 2.5',
+ 0x010b => 'Hi 2.7',
+ 0x010c => 'Hi 3.0',
+ 0x010d => 'Hi 3.3',
+ 0x010e => 'Hi 3.5',
+ 0x010f => 'Hi 3.7',
+ 0x0110 => 'Hi 4.0',
+ 0x0111 => 'Hi 4.3',
+ 0x0112 => 'Hi 4.5',
+ 0x0113 => 'Hi 4.7',
+ 0x0114 => 'Hi 5.0',
+ 0x0201 => 'Lo 0.3',
+ 0x0202 => 'Lo 0.5',
+ 0x0203 => 'Lo 0.7',
+ 0x0204 => 'Lo 1.0',
+ );
+
+ protected static $isoInfoExpansions2 = array(
+ 0x0000 => 'Off',
+ 0x0101 => 'Hi 0.3',
+ 0x0102 => 'Hi 0.5',
+ 0x0103 => 'Hi 0.7',
+ 0x0104 => 'Hi 1.0',
+ 0x0105 => 'Hi 1.3',
+ 0x0106 => 'Hi 1.5',
+ 0x0107 => 'Hi 1.7',
+ 0x0108 => 'Hi 2.0',
+ 0x0201 => 'Lo 0.3',
+ 0x0202 => 'Lo 0.5',
+ 0x0203 => 'Lo 0.7',
+ 0x0204 => 'Lo 1.0',
+ );
+
+ protected static $vignetteControls = array(
+ 0 => 'Off',
+ 1 => 'Low',
+ 3 => 'Normal',
+ 5 => 'High'
+ );
+
+ protected static $flashModes = array(
+ 0 => 'Did Not Fire',
+ 1 => 'Fired, Manual',
+ 3 => 'Not Ready',
+ 7 => 'Fired, External',
+ 8 => 'Fired, Commander Mode',
+ 9 => 'Fired, TTL Mode',
+ 18 => 'LED Light'
+ );
+
+ protected static $flashInfoSources = array(
+ 0 => 'None',
+ 1 => 'External',
+ 2 => 'Internal'
+ );
+
+ protected static $flashInfoExternalFlashFirmwares = array(
+ '0 0' => 'n/a',
+ '1 1' => '1.01 (SB-800 or Metz 58 AF-1)',
+ '1 3' => '1.03 (SB-800)',
+ '2 1' => '2.01 (SB-800)',
+ '2 4' => '2.04 (SB-600)',
+ '2 5' => '2.05 (SB-600)',
+ '3 1' => '3.01 (SU-800 Remote Commander)',
+ '4 1' => '4.01 (SB-400)',
+ '4 2' => '4.02 (SB-400)',
+ '4 4' => '4.04 (SB-400)',
+ '5 1' => '5.01 (SB-900)',
+ '5 2' => '5.02 (SB-900)',
+ '6 1' => '6.01 (SB-700)',
+ '7 1' => '7.01 (SB-910)',
+ );
+
+ protected static $flashInfoExternalFlashFlags = array(
+ 0 => 'Fired',
+ 2 => 'Bounce Flash',
+ 4 => 'Wide Flash Adapter',
+ 5 => 'Dome Diffuser',
+ );
+
+ protected static $flashInfoExternalFlashStatuses = array(
+ 0 => 'Flash Not Attached',
+ 1 => 'Flash Attached',
+ );
+
+ protected static $flashInfoExternalFlashReadyStates = array(
+ 0 => 'n/a',
+ 1 => 'Ready',
+ 6 => 'Not Ready',
+ );
+
+ protected static $flashInfoGNDistances = array(
+ 0 => 0, 19 => '2.8 m',
+ 1 => '0.1 m', 20 => '3.2 m',
+ 2 => '0.2 m', 21 => '3.6 m',
+ 3 => '0.3 m', 22 => '4.0 m',
+ 4 => '0.4 m', 23 => '4.5 m',
+ 5 => '0.5 m', 24 => '5.0 m',
+ 6 => '0.6 m', 25 => '5.6 m',
+ 7 => '0.7 m', 26 => '6.3 m',
+ 8 => '0.8 m', 27 => '7.1 m',
+ 9 => '0.9 m', 28 => '8.0 m',
+ 10 => '1.0 m', 29 => '9.0 m',
+ 11 => '1.1 m', 30 => '10.0 m',
+ 12 => '1.3 m', 31 => '11.0 m',
+ 13 => '1.4 m', 32 => '13.0 m',
+ 14 => '1.6 m', 33 => '14.0 m',
+ 15 => '1.8 m', 34 => '16.0 m',
+ 16 => '2.0 m', 35 => '18.0 m',
+ 17 => '2.2 m', 36 => '20.0 m',
+ 18 => '2.5 m', 255 => 'n/a'
+ );
+
+ protected static $flashInfoControlModes = array(
+ 0x00 => 'Off',
+ 0x01 => 'iTTL-BL',
+ 0x02 => 'iTTL',
+ 0x03 => 'Auto Aperture',
+ 0x04 => 'Automatic',
+ 0x05 => 'GN (distance priority)',
+ 0x06 => 'Manual',
+ 0x07 => 'Repeating Flash',
+ );
+
+ protected static $flashInfoColorFilters = array(
+ 0 => 'None',
+ 1 => 'FL-GL1 or SZ-2FL Fluorescent',
+ 2 => 'FL-GL2',
+ 9 => 'TN-A1 or SZ-2TN Incandescent',
+ 10 => 'TN-A2',
+ 65 => 'Red',
+ 66 => 'Blue',
+ 67 => 'Yellow',
+ 68 => 'Amber',
+ );
+
+ protected static $highISONoiseReductions = array(
+ 0 => 'Off',
+ 1 => 'Minimal',
+ 2 => 'Low',
+ 3 => 'Medium Low',
+ 4 => 'Normal',
+ 5 => 'Medium High',
+ 6 => 'High'
+ );
+
+ protected static $AFInfo2ContrastDetectAFChoices = array(
+ 0 => 'Off',
+ 1 => 'On',
+ 2 => 'On (2)'
+ );
+
+ protected static $AFInfo2AFAreaModesWithoutContrastDetectAF = array(
+ 0 => 'Single Area',
+ 1 => 'Dynamic Area',
+ 2 => 'Dynamic Area (closest subject)',
+ 3 => 'Group Dynamic',
+ 4 => 'Dynamic Area (9 points)',
+ 5 => 'Dynamic Area (21 points)',
+ 6 => 'Dynamic Area (51 points)',
+ 7 => 'Dynamic Area (51 points, 3D-tracking)',
+ 8 => 'Auto-area',
+ 9 => 'Dynamic Area (3D-tracking)',
+ 10 => 'Single Area (wide)',
+ 11 => 'Dynamic Area (wide)',
+ 12 => 'Dynamic Area (wide, 3D-tracking)',
+ 13 => 'Group Area',
+ 14 => 'Dynamic Area (25 points)',
+ 15 => 'Dynamic Area (72 points)',
+ 16 => 'Group Area (HL)',
+ 17 => 'Group Area (VL)',
+ 18 => 'Dynamic Area (49 points)',
+ 128 => 'Single',
+ 129 => 'Auto (41 points)',
+ 130 => 'Subject Tracking (41 points)',
+ 131 => 'Face Priority (41 points)',
+ 192 => 'Pinpoint',
+ 193 => 'Single',
+ 195 => 'Wide (S)',
+ 196 => 'Wide (L)',
+ 197 => 'Auto',
+ );
+
+ protected static $AFInfo2AFAreaModesWithContrastDetectAF = array(
+ 0 => 'Contrast-detect',
+ 1 => 'Contrast-detect (normal area)',
+ 2 => 'Contrast-detect (wide area)',
+ 3 => 'Contrast-detect (face priority)',
+ 4 => 'Contrast-detect (subject tracking)',
+ 128 => 'Single',
+ 129 => 'Auto (41 points)',
+ 130 => 'Subject Tracking (41 points)',
+ 131 => 'Face Priority (41 points)',
+ 192 => 'Pinpoint',
+ 193 => 'Single',
+ 194 => 'Dynamic',
+ 195 => 'Wide (S)',
+ 196 => 'Wide (L)',
+ 197 => 'Auto',
+ 198 => 'Auto (People)',
+ 199 => 'Auto (Animal)',
+ 200 => 'Normal-area AF',
+ 201 => 'Wide-area AF',
+ 202 => 'Face-priority AF',
+ 203 => 'Subject-tracking AF',
+ );
+
+ protected static $AFInfo2PhaseDetectAFChoices = array(
+ 0 => 'Off',
+ 1 => 'On (51-point)',
+ 2 => 'On (11-point)',
+ 3 => 'On (39-point)',
+ 4 => 'On (73-point)',
+ 5 => 'On (5)',
+ 6 => 'On (105-point)',
+ 7 => 'On (153-point)',
+ 8 => 'On (81-point)',
+ 9 => 'On (105-point)',
+ );
+
+ protected static $NikkorZLensIDS = array(
+ 1 => 'Nikkor Z 24-70mm f/4 S',
+ 2 => 'Nikkor Z 14-30mm f/4 S',
+ 4 => 'Nikkor Z 35mm f/1.8 S',
+ 8 => 'Nikkor Z 58mm f/0.95 S Noct',
+ 9 => 'Nikkor Z 50mm f/1.8 S',
+ 11 => 'Nikkor Z DX 16-50mm f/3.5-6.3 VR',
+ 12 => 'Nikkor Z DX 50-250mm f/4.5-6.3 VR',
+ 13 => 'Nikkor Z 24-70mm f/2.8 S',
+ 14 => 'Nikkor Z 85mm f/1.8 S',
+ 15 => 'Nikkor Z 24mm f/1.8 S',
+ 16 => 'Nikkor Z 70-200mm f/2.8 VR S',
+ 17 => 'Nikkor Z 20mm f/1.8 S',
+ 18 => 'Nikkor Z 24-200mm f/4-6.3 VR',
+ 21 => 'Nikkor Z 50mm f/1.2 S',
+ 22 => 'Nikkor Z 24-50mm f/4-6.3',
+ 23 => 'Nikkor Z 14-24mm f/2.8 S',
+ );
+
+ protected static $nikonTextEncodings = array(
+ 1 => 'UTF-8',
+ 2 => 'UTF-16'
+ );
+
+ /**
+ * Ref 4
+ *
+ * @var int[][]
+ */
+ protected static $decodeTables = array(
+ array(
+ 0xc1,0xbf,0x6d,0x0d,0x59,0xc5,0x13,0x9d,0x83,0x61,0x6b,0x4f,0xc7,0x7f,0x3d,0x3d,
+ 0x53,0x59,0xe3,0xc7,0xe9,0x2f,0x95,0xa7,0x95,0x1f,0xdf,0x7f,0x2b,0x29,0xc7,0x0d,
+ 0xdf,0x07,0xef,0x71,0x89,0x3d,0x13,0x3d,0x3b,0x13,0xfb,0x0d,0x89,0xc1,0x65,0x1f,
+ 0xb3,0x0d,0x6b,0x29,0xe3,0xfb,0xef,0xa3,0x6b,0x47,0x7f,0x95,0x35,0xa7,0x47,0x4f,
+ 0xc7,0xf1,0x59,0x95,0x35,0x11,0x29,0x61,0xf1,0x3d,0xb3,0x2b,0x0d,0x43,0x89,0xc1,
+ 0x9d,0x9d,0x89,0x65,0xf1,0xe9,0xdf,0xbf,0x3d,0x7f,0x53,0x97,0xe5,0xe9,0x95,0x17,
+ 0x1d,0x3d,0x8b,0xfb,0xc7,0xe3,0x67,0xa7,0x07,0xf1,0x71,0xa7,0x53,0xb5,0x29,0x89,
+ 0xe5,0x2b,0xa7,0x17,0x29,0xe9,0x4f,0xc5,0x65,0x6d,0x6b,0xef,0x0d,0x89,0x49,0x2f,
+ 0xb3,0x43,0x53,0x65,0x1d,0x49,0xa3,0x13,0x89,0x59,0xef,0x6b,0xef,0x65,0x1d,0x0b,
+ 0x59,0x13,0xe3,0x4f,0x9d,0xb3,0x29,0x43,0x2b,0x07,0x1d,0x95,0x59,0x59,0x47,0xfb,
+ 0xe5,0xe9,0x61,0x47,0x2f,0x35,0x7f,0x17,0x7f,0xef,0x7f,0x95,0x95,0x71,0xd3,0xa3,
+ 0x0b,0x71,0xa3,0xad,0x0b,0x3b,0xb5,0xfb,0xa3,0xbf,0x4f,0x83,0x1d,0xad,0xe9,0x2f,
+ 0x71,0x65,0xa3,0xe5,0x07,0x35,0x3d,0x0d,0xb5,0xe9,0xe5,0x47,0x3b,0x9d,0xef,0x35,
+ 0xa3,0xbf,0xb3,0xdf,0x53,0xd3,0x97,0x53,0x49,0x71,0x07,0x35,0x61,0x71,0x2f,0x43,
+ 0x2f,0x11,0xdf,0x17,0x97,0xfb,0x95,0x3b,0x7f,0x6b,0xd3,0x25,0xbf,0xad,0xc7,0xc5,
+ 0xc5,0xb5,0x8b,0xef,0x2f,0xd3,0x07,0x6b,0x25,0x49,0x95,0x25,0x49,0x6d,0x71,0xc7
+ ),
+ array(
+ 0xa7,0xbc,0xc9,0xad,0x91,0xdf,0x85,0xe5,0xd4,0x78,0xd5,0x17,0x46,0x7c,0x29,0x4c,
+ 0x4d,0x03,0xe9,0x25,0x68,0x11,0x86,0xb3,0xbd,0xf7,0x6f,0x61,0x22,0xa2,0x26,0x34,
+ 0x2a,0xbe,0x1e,0x46,0x14,0x68,0x9d,0x44,0x18,0xc2,0x40,0xf4,0x7e,0x5f,0x1b,0xad,
+ 0x0b,0x94,0xb6,0x67,0xb4,0x0b,0xe1,0xea,0x95,0x9c,0x66,0xdc,0xe7,0x5d,0x6c,0x05,
+ 0xda,0xd5,0xdf,0x7a,0xef,0xf6,0xdb,0x1f,0x82,0x4c,0xc0,0x68,0x47,0xa1,0xbd,0xee,
+ 0x39,0x50,0x56,0x4a,0xdd,0xdf,0xa5,0xf8,0xc6,0xda,0xca,0x90,0xca,0x01,0x42,0x9d,
+ 0x8b,0x0c,0x73,0x43,0x75,0x05,0x94,0xde,0x24,0xb3,0x80,0x34,0xe5,0x2c,0xdc,0x9b,
+ 0x3f,0xca,0x33,0x45,0xd0,0xdb,0x5f,0xf5,0x52,0xc3,0x21,0xda,0xe2,0x22,0x72,0x6b,
+ 0x3e,0xd0,0x5b,0xa8,0x87,0x8c,0x06,0x5d,0x0f,0xdd,0x09,0x19,0x93,0xd0,0xb9,0xfc,
+ 0x8b,0x0f,0x84,0x60,0x33,0x1c,0x9b,0x45,0xf1,0xf0,0xa3,0x94,0x3a,0x12,0x77,0x33,
+ 0x4d,0x44,0x78,0x28,0x3c,0x9e,0xfd,0x65,0x57,0x16,0x94,0x6b,0xfb,0x59,0xd0,0xc8,
+ 0x22,0x36,0xdb,0xd2,0x63,0x98,0x43,0xa1,0x04,0x87,0x86,0xf7,0xa6,0x26,0xbb,0xd6,
+ 0x59,0x4d,0xbf,0x6a,0x2e,0xaa,0x2b,0xef,0xe6,0x78,0xb6,0x4e,0xe0,0x2f,0xdc,0x7c,
+ 0xbe,0x57,0x19,0x32,0x7e,0x2a,0xd0,0xb8,0xba,0x29,0x00,0x3c,0x52,0x7d,0xa8,0x49,
+ 0x3b,0x2d,0xeb,0x25,0x49,0xfa,0xa3,0xaa,0x39,0xa7,0xc5,0xa7,0x50,0x11,0x36,0xfb,
+ 0xc6,0x67,0x4a,0xf5,0xa5,0x12,0x65,0x7e,0xb0,0xdf,0xaf,0x4e,0xb3,0x61,0x7f,0x2f
+ )
+ );
+
+ /**
+ * @var GetID3
+ */
+ private $getid3;
+
+ public function __construct(GetID3 $getid3)
+ {
+ $this->getid3 = $getid3;
+ }
+
+ /**
+ * Get a copy of all NCTG tags extracted from the video
+ *
+ * @param string $atomData
+ *
+ * @return array
+ */
+ public function parse($atomData) {
+ // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
+ // Data is stored as records of:
+ // * 4 bytes record type
+ // * 2 bytes size of data field type:
+ // 0x0001 = flag / unsigned byte (size field *= 1-byte)
+ // 0x0002 = char / ascii strings (size field *= 1-byte)
+ // 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
+ // 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
+ // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ // 0x0006 = signed byte (size field *= 1-byte)
+ // 0x0007 = raw bytes (size field *= 1-byte)
+ // 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
+ // 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
+ // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ // * 2 bytes data size field
+ // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
+ // all integers are stored BigEndian
+
+ $NCTGtagName = array(
+ 0x00000001 => 'Make',
+ 0x00000002 => 'Model',
+ 0x00000003 => 'Software',
+ 0x00000011 => 'CreateDate',
+ 0x00000012 => 'DateTimeOriginal',
+ 0x00000013 => 'FrameCount',
+ 0x00000016 => 'FrameRate',
+ 0x00000019 => 'TimeZone',
+ 0x00000022 => 'FrameWidth',
+ 0x00000023 => 'FrameHeight',
+ 0x00000032 => 'AudioChannels',
+ 0x00000033 => 'AudioBitsPerSample',
+ 0x00000034 => 'AudioSampleRate',
+ 0x00001002 => 'NikonDateTime',
+ 0x00001013 => 'ElectronicVR',
+ 0x0110829a => 'ExposureTime',
+ 0x0110829d => 'FNumber',
+ 0x01108822 => 'ExposureProgram',
+ 0x01109204 => 'ExposureCompensation',
+ 0x01109207 => 'MeteringMode',
+ 0x0110920a => 'FocalLength', // mm
+ 0x0110a431 => 'SerialNumber',
+ 0x0110a432 => 'LensInfo',
+ 0x0110a433 => 'LensMake',
+ 0x0110a434 => 'LensModel',
+ 0x0110a435 => 'LensSerialNumber',
+ 0x01200000 => 'GPSVersionID',
+ 0x01200001 => 'GPSLatitudeRef',
+ 0x01200002 => 'GPSLatitude',
+ 0x01200003 => 'GPSLongitudeRef',
+ 0x01200004 => 'GPSLongitude',
+ 0x01200005 => 'GPSAltitudeRef', // 0 = Above Sea Level, 1 = Below Sea Level
+ 0x01200006 => 'GPSAltitude',
+ 0x01200007 => 'GPSTimeStamp',
+ 0x01200008 => 'GPSSatellites',
+ 0x01200010 => 'GPSImgDirectionRef', // M = Magnetic North, T = True North
+ 0x01200011 => 'GPSImgDirection',
+ 0x01200012 => 'GPSMapDatum',
+ 0x0120001d => 'GPSDateStamp',
+ 0x02000001 => 'MakerNoteVersion',
+ 0x02000005 => 'WhiteBalance',
+ 0x02000007 => 'FocusMode',
+ 0x0200000b => 'WhiteBalanceFineTune',
+ 0x0200001b => 'CropHiSpeed',
+ 0x0200001e => 'ColorSpace',
+ 0x0200001f => 'VRInfo',
+ 0x02000022 => 'ActiveDLighting',
+ 0x02000023 => 'PictureControlData',
+ 0x02000024 => 'WorldTime',
+ 0x02000025 => 'ISOInfo',
+ 0x0200002a => 'VignetteControl',
+ 0x0200002c => 'UnknownInfo',
+ 0x02000032 => 'UnknownInfo2',
+ 0x02000039 => 'LocationInfo',
+ 0x02000083 => 'LensType',
+ 0x02000084 => 'Lens',
+ 0x02000087 => 'FlashMode',
+ 0x02000098 => 'LensData',
+ 0x020000a7 => 'ShutterCount',
+ 0x020000a8 => 'FlashInfo',
+ 0x020000ab => 'VariProgram',
+ 0x020000b1 => 'HighISONoiseReduction',
+ 0x020000b7 => 'AFInfo2',
+ 0x020000c3 => 'BarometerInfo',
+ );
+
+ $firstPassNeededTags = array(
+ 0x00000002, // Model
+ 0x0110a431, // SerialNumber
+ 0x020000a7, // ShutterCount
+ );
+
+ $datalength = strlen($atomData);
+ $parsed = array();
+ $model = $serialNumber = $shutterCount = null;
+ for ($pass = 0; $pass < 2; ++$pass) {
+ $offset = 0;
+ $parsed = array();
+ $data = null;
+ while ($offset < $datalength) {
+ $record_type = Utils::BigEndian2Int(substr($atomData, $offset, 4));
+ $offset += 4;
+ $data_size_type = Utils::BigEndian2Int(substr($atomData, $offset, 2));
+ $data_size = static::$exifTypeSizes[$data_size_type];
+ $offset += 2;
+ $data_count = Utils::BigEndian2Int(substr($atomData, $offset, 2));
+ $offset += 2;
+ $data = array();
+
+ if ($pass === 0 && !in_array($record_type, $firstPassNeededTags, true)) {
+ $offset += $data_count * $data_size;
+ continue;
+ }
+
+ switch ($data_size_type) {
+ case self::EXIF_TYPE_UINT8: // 0x0001 = flag / unsigned byte (size field *= 1-byte)
+ for ($i = 0; $i < $data_count; ++$i) {
+ $data[] = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_CHAR: // 0x0002 = char / ascii strings (size field *= 1-byte)
+ $data = substr($atomData, $offset, $data_count * $data_size);
+ $offset += ($data_count * $data_size);
+ $data = rtrim($data, "\x00");
+ break;
+ case self::EXIF_TYPE_UINT16: // 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
+ for ($i = 0; $i < $data_count; ++$i) {
+ $data[] = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_UINT32: // 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
+ // нужно проверить FrameCount
+ for ($i = 0; $i < $data_count; ++$i) {
+ $data[] = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_URATIONAL: // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ for ($i = 0; $i < $data_count; ++$i) {
+ $numerator = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4));
+ $denomninator = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4));
+ if ($denomninator == 0) {
+ $data[] = false;
+ } else {
+ $data[] = (float)$numerator / $denomninator;
+ }
+ }
+ $offset += ($data_size * $data_count);
+ break;
+ case self::EXIF_TYPE_INT8: // 0x0006 = bytes / signed byte (size field *= 1-byte)
+ // NOT TESTED
+ for ($i = 0; $i < $data_count; ++$i) {
+ $data[] = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_RAW: // 0x0007 = raw bytes (size field *= 1-byte)
+ $data = substr($atomData, $offset, $data_count * $data_size);
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_INT16: // 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
+ for ($i = 0; $i < $data_count; ++$i) {
+ $value = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+ if ($value >= 0x8000) {
+ $value -= 0x10000;
+ }
+ $data[] = $value;
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_INT32: // 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
+ // NOT TESTED
+ for ($i = 0; $i < $data_count; ++$i) {
+ $data = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
+ }
+ $offset += ($data_count * $data_size);
+ break;
+ case self::EXIF_TYPE_RATIONAL: // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ // NOT TESTED
+ for ($i = 0; $i < $data_count; ++$i) {
+ $numerator = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4), false, true);
+ $denomninator = Utils::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4), false, true);
+ if ($denomninator == 0) {
+ $data[] = false;
+ } else {
+ $data[] = (float)$numerator / $denomninator;
+ }
+ }
+ $offset += ($data_size * $data_count);
+ if (count($data) == 1) {
+ $data = $data[0];
+ }
+ break;
+ default:
+ $this->getid3->warning('QuicktimeParseNikonNCTG()::unknown $data_size_type: ' . $data_size_type);
+ break 2;
+ }
+
+ if (is_array($data) && count($data) === 1) {
+ $data = $data[0];
+ }
+
+ switch ($record_type) {
+ case 0x00000002:
+ $model = $data;
+ break;
+ case 0x00000013: // FrameCount
+ if (is_array($data) && count($data) === 2 && $data[1] == 0) {
+ $data = $data[0];
+ }
+ break;
+ case 0x00000011: // CreateDate
+ case 0x00000012: // DateTimeOriginal
+ case 0x00001002: // NikonDateTime
+ $data = strtotime($data);
+ break;
+ case 0x00001013: // ElectronicVR
+ $data = (bool) $data;
+ break;
+ case 0x0110829a: // ExposureTime
+ // Print exposure time as a fraction
+ /** @var float $data */
+ if ($data < 0.25001 && $data > 0) {
+ $data = sprintf("1/%d", intval(0.5 + 1 / $data));
+ }
+ break;
+ case 0x01109204: // ExposureCompensation
+ $data = $this->printFraction($data);
+ break;
+ case 0x01108822: // ExposureProgram
+ $data = isset(static::$exposurePrograms[$data]) ? static::$exposurePrograms[$data] : $data;
+ break;
+ case 0x01109207: // MeteringMode
+ $data = isset(static::$meteringModes[$data]) ? static::$meteringModes[$data] : $data;
+ break;
+ case 0x0110a431: // SerialNumber
+ $serialNumber = $this->serialKey($data, $model);
+ break;
+ case 0x01200000: // GPSVersionID
+ $parsed['GPS']['computed']['version'] = 'v'.implode('.', $data);
+ break;
+ case 0x01200002: // GPSLatitude
+ if (is_array($data)) {
+ $direction_multiplier = ((isset($parsed['GPSLatitudeRef']) && ($parsed['GPSLatitudeRef'] === 'S')) ? -1 : 1);
+ $parsed['GPS']['computed']['latitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
+ }
+ break;
+ case 0x01200004: // GPSLongitude
+ if (is_array($data)) {
+ $direction_multiplier = ((isset($parsed['GPSLongitudeRef']) && ($parsed['GPSLongitudeRef'] === 'W')) ? -1 : 1);
+ $parsed['GPS']['computed']['longitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
+ }
+ break;
+ case 0x01200006: // GPSAltitude
+ if (isset($parsed['GPSAltitudeRef'])) {
+ $direction_multiplier = (!empty($parsed['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
+ $parsed['GPS']['computed']['altitude'] = $direction_multiplier * $data;
+ }
+ break;
+ case 0x0120001d: // GPSDateStamp
+ if (isset($parsed['GPSTimeStamp']) && is_array($parsed['GPSTimeStamp']) && $data !== '') {
+ $explodedDate = explode(':', $data);
+ $parsed['GPS']['computed']['timestamp'] = gmmktime($parsed['GPSTimeStamp'][0], $parsed['GPSTimeStamp'][1], $parsed['GPSTimeStamp'][2], $explodedDate[1], $explodedDate[2], $explodedDate[0]);
+ }
+ break;
+ case 0x02000001: // MakerNoteVersion
+ $data = ltrim(substr($data, 0, 2) . '.' . substr($data, 2, 2), '0');
+ break;
+ case 0x0200001b: // CropHiSpeed
+ if (is_array($data) && count($data) === 7) {
+ $name = isset(static::$cropHiSpeeds[$data[0]]) ? static::$cropHiSpeeds[$data[0]] : sprintf('Unknown (%d)', $data[0]);
+ $data = array(
+ 'Name' => $name,
+ 'OriginalWidth' => $data[1],
+ 'OriginalHeight' => $data[2],
+ 'CroppedWidth' => $data[3],
+ 'CroppedHeight' => $data[4],
+ 'PixelXPosition' => $data[5],
+ 'PixelYPosition' => $data[6],
+ );
+ }
+ break;
+ case 0x0200001e: // ColorSpace
+ $data = isset(static::$colorSpaces[$data]) ? static::$colorSpaces[$data] : $data;
+ break;
+ case 0x0200001f: // VRInfo
+ $data = array(
+ 'VRInfoVersion' => substr($data, 0, 4),
+ 'VibrationReduction' => isset(static::$vibrationReductions[ord(substr($data, 4, 1))])
+ ? static::$vibrationReductions[ord(substr($data, 4, 1))]
+ : null,
+ 'VRMode' => static::$VRModes[ord(substr($data, 6, 1))],
+ );
+ break;
+ case 0x02000022: // ActiveDLighting
+ $data = isset(static::$activeDLightnings[$data]) ? static::$activeDLightnings[$data] : $data;
+ break;
+ case 0x02000023: // PictureControlData
+ switch (substr($data, 0, 2)) {
+ case '01':
+ $data = array(
+ 'PictureControlVersion' => substr($data, 0, 4),
+ 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
+ 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
+ //'?' => substr($data, 44, 4),
+ 'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
+ 'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
+ 'Sharpness' => $this->printPC(ord(substr($data, 50, 1)) - 0x80, 'No Sharpening', '%d'),
+ 'Contrast' => $this->printPC(ord(substr($data, 51, 1)) - 0x80),
+ 'Brightness' => $this->printPC(ord(substr($data, 52, 1)) - 0x80),
+ 'Saturation' => $this->printPC(ord(substr($data, 53, 1)) - 0x80),
+ 'HueAdjustment' => $this->printPC(ord(substr($data, 54, 1)) - 0x80, 'None'),
+ 'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 55, 1))],
+ 'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 56, 1))],
+ 'ToningSaturation' => $this->printPC(ord(substr($data, 57, 1)) - 0x80),
+ );
+ break;
+ case '02':
+ $data = array(
+ 'PictureControlVersion' => substr($data, 0, 4),
+ 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
+ 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
+ //'?' => substr($data, 44, 4),
+ 'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
+ 'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
+ 'Sharpness' => $this->printPC(ord(substr($data, 51, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Clarity' => $this->printPC(ord(substr($data, 53, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Contrast' => $this->printPC(ord(substr($data, 55, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Brightness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'Normal', '%.2f', 4),
+ 'Saturation' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Hue' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
+ 'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 63, 1))],
+ 'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 64, 1))],
+ 'ToningSaturation' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'None', '%.2f', 4),
+ );
+ break;
+ case '03':
+ $data = array(
+ 'PictureControlVersion' => substr($data, 0, 4),
+ 'PictureControlName' => rtrim(substr($data, 8, 20), "\x00"),
+ 'PictureControlBase' => rtrim(substr($data, 28, 20), "\x00"),
+ 'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 54, 1))],
+ 'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 55, 1)) - 0x80),
+ 'Sharpness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'None', '%.2f', 4),
+ 'MidRangeSharpness' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Clarity' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Contrast' => $this->printPC(ord(substr($data, 63, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Brightness' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'Normal', '%.2f', 4),
+ 'Saturation' => $this->printPC(ord(substr($data, 67, 1)) - 0x80, 'None', '%.2f', 4),
+ 'Hue' => $this->printPC(ord(substr($data, 69, 1)) - 0x80, 'None', '%.2f', 4),
+ 'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 71, 1))],
+ 'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 72, 1))],
+ 'ToningSaturation' => $this->printPC(ord(substr($data, 73, 1)) - 0x80, 'None', '%.2f', 4),
+ );
+ break;
+ default:
+ $data = array(
+ 'PictureControlVersion' => substr($data, 0, 4),
+ );
+ break;
+ }
+ break;
+ case 0x02000024: // WorldTime
+ // https://exiftool.org/TagNames/Nikon.html#WorldTime
+ // timezone is stored as offset from GMT in minutes
+ $timezone = Utils::BigEndian2Int(substr($data, 0, 2));
+ if ($timezone & 0x8000) {
+ $timezone = 0 - (0x10000 - $timezone);
+ }
+ $hours = (int)abs($timezone / 60);
+ $minutes = abs($timezone) - $hours * 60;
+
+ $dst = (bool)Utils::BigEndian2Int(substr($data, 2, 1));
+ switch (Utils::BigEndian2Int(substr($data, 3, 1))) {
+ case 2:
+ $datedisplayformat = 'D/M/Y';
+ break;
+ case 1:
+ $datedisplayformat = 'M/D/Y';
+ break;
+ case 0:
+ default:
+ $datedisplayformat = 'Y/M/D';
+ break;
+ }
+
+ $data = array(
+ 'timezone' => sprintf('%s%02d:%02d', $timezone >= 0 ? '+' : '-', $hours, $minutes),
+ 'dst' => $dst,
+ 'display' => $datedisplayformat
+ );
+ break;
+ case 0x02000025: // ISOInfo
+ $data = array(
+ 'ISO' => (int)ceil(100 * pow(2, ord(substr($data, 0, 1)) / 12 - 5)),
+ 'ISOExpansion' => static::$isoInfoExpansions[Utils::BigEndian2Int(substr($data, 4, 2))],
+ 'ISO2' => (int)ceil(100 * pow(2, ord(substr($data, 6, 1)) / 12 - 5)),
+ 'ISOExpansion2' => static::$isoInfoExpansions2[Utils::BigEndian2Int(substr($data, 10, 2))]
+ );
+ break;
+ case 0x0200002a: // VignetteControl
+ $data = isset(static::$vignetteControls[$data]) ? static::$vignetteControls[$data] : $data;
+ break;
+ case 0x0200002c: // UnknownInfo
+ $data = array(
+ 'UnknownInfoVersion' => substr($data, 0, 4),
+ );
+ break;
+ case 0x02000032: // UnknownInfo2
+ $data = array(
+ 'UnknownInfo2Version' => substr($data, 0, 4),
+ );
+ break;
+ case 0x02000039: // LocationInfo
+ $encoding = isset(static::$nikonTextEncodings[ord(substr($data, 4, 1))])
+ ? static::$nikonTextEncodings[ord(substr($data, 4, 1))]
+ : null;
+ $data = array(
+ 'LocationInfoVersion' => substr($data, 0, 4),
+ 'TextEncoding' => $encoding,
+ 'CountryCode' => trim(substr($data, 5, 3), "\x00"),
+ 'POILevel' => ord(substr($data, 8, 1)),
+ 'Location' => Utils::iconv_fallback($encoding, $this->getid3->info['encoding'], substr($data, 9, 70)),
+ );
+ break;
+ case 0x02000083: // LensType
+ if ($data) {
+ $decodedBits = array(
+ '1' => (bool) (($data >> 4) & 1),
+ 'MF' => (bool) (($data >> 0) & 1),
+ 'D' => (bool) (($data >> 1) & 1),
+ 'E' => (bool) (($data >> 6) & 1),
+ 'G' => (bool) (($data >> 2) & 1),
+ 'VR' => (bool) (($data >> 3) & 1),
+ '[7]' => (bool) (($data >> 7) & 1), // AF-P?
+ '[8]' => (bool) (($data >> 5) & 1) // FT-1?
+ );
+ if ($decodedBits['D'] === true && $decodedBits['G'] === true) {
+ $decodedBits['D'] = false;
+ }
+ } else {
+ $decodedBits = array('AF' => true);
+ }
+ $data = $decodedBits;
+ break;
+ case 0x0110a432: // LensInfo
+ case 0x02000084: // Lens
+ if (count($data) !== 4) {
+ break;
+ }
+
+ $value = $data[0];
+ if ($data[1] && $data[1] !== $data[0]) {
+ $value .= '-' . $data[1];
+ }
+ $value .= 'mm f/' . $data[2];
+ if ($data[3] && $data[3] !== $data[2]) {
+ $value .= '-' . $data[3];
+ }
+ $data = $value;
+ break;
+ case 0x02000087: // FlashMode
+ $data = isset(static::$flashModes[$data]) ? static::$flashModes[$data] : $data;
+ break;
+ case 0x02000098: // LensData
+ $version = substr($data, 0, 4);
+
+ switch ($version) {
+ case '0100':
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'LensIDNumber' => ord(substr($data, 6, 1)),
+ 'LensFStops' => ord(substr($data, 7, 1)) / 12,
+ 'MinFocalLength' => 5 * pow(2, ord(substr($data, 8, 1)) / 24), // mm
+ 'MaxFocalLength' => 5 * pow(2, ord(substr($data, 9, 1)) / 24), // mm
+ 'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 10, 1)) / 24),
+ 'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 11, 1)) / 24),
+ 'MCUVersion' => ord(substr($data, 12, 1)),
+ );
+ break;
+ case '0101':
+ case '0201':
+ case '0202':
+ case '0203':
+ $isEncrypted = $version !== '0101';
+ if ($isEncrypted) {
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+ }
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
+ 'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
+ 'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
+ 'FocusDistance' => 0.01 * pow(10, ord(substr($data, 9, 1)) / 40), // m
+ 'FocalLength' => 5 * pow(2, ord(substr($data, 10, 1)) / 24), // mm
+ 'LensIDNumber' => ord(substr($data, 11, 1)),
+ 'LensFStops' => ord(substr($data, 12, 1)) / 12,
+ 'MinFocalLength' => 5 * pow(2, ord(substr($data, 13, 1)) / 24), // mm
+ 'MaxFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
+ 'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 15, 1)) / 24),
+ 'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
+ 'MCUVersion' => ord(substr($data, 17, 1)),
+ 'EffectiveMaxAperture' => pow(2, ord(substr($data, 18, 1)) / 24),
+ );
+ break;
+ case '0204':
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
+ 'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
+ 'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
+ 'FocusDistance' => 0.01 * pow(10, ord(substr($data, 10, 1)) / 40), // m
+ 'FocalLength' => 5 * pow(2, ord(substr($data, 11, 1)) / 24), // mm
+ 'LensIDNumber' => ord(substr($data, 12, 1)),
+ 'LensFStops' => ord(substr($data, 13, 1)) / 12,
+ 'MinFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
+ 'MaxFocalLength' => 5 * pow(2, ord(substr($data, 15, 1)) / 24), // mm
+ 'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
+ 'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 17, 1)) / 24),
+ 'MCUVersion' => ord(substr($data, 18, 1)),
+ 'EffectiveMaxAperture' => pow(2, ord(substr($data, 19, 1)) / 24),
+ );
+ break;
+ case '0400':
+ case '0401':
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'LensModel' => substr($data, 394, 64),
+ );
+ break;
+ case '0402':
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'LensModel' => substr($data, 395, 64),
+ );
+ break;
+ case '0403':
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ 'LensModel' => substr($data, 684, 64),
+ );
+ break;
+ case '0800':
+ case '0801':
+ $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $newData = array(
+ 'LensDataVersion' => $version,
+ );
+
+ if (!preg_match('#^.\0+#s', substr($data, 3, 17))) {
+ $newData['ExitPupilPosition'] = ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0; // mm
+ $newData['AFAperture'] = pow(2, ord(substr($data, 5, 1)) / 24);
+ $newData['FocusPosition'] = '0x' . str_pad(strtoupper(dechex(ord(substr($data, 9, 1)))), 2, '0', STR_PAD_LEFT);
+ $newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 11, 1)) / 40); // m
+ $newData['FocalLength'] = 5 * pow(2, ord(substr($data, 12, 1)) / 24); // mm
+ $newData['LensIDNumber'] = ord(substr($data, 13, 1));
+ $newData['LensFStops'] = ord(substr($data, 14, 1)) / 12;
+ $newData['MinFocalLength'] = 5 * pow(2, ord(substr($data, 15, 1)) / 24); // mm
+ $newData['MaxFocalLength'] = 5 * pow(2, ord(substr($data, 16, 1)) / 24); // mm
+ $newData['MaxApertureAtMinFocal'] = pow(2, ord(substr($data, 17, 1)) / 24);
+ $newData['MaxApertureAtMaxFocal'] = pow(2, ord(substr($data, 18, 1)) / 24);
+ $newData['MCUVersion'] = ord(substr($data, 19, 1));
+ $newData['EffectiveMaxAperture'] = pow(2, ord(substr($data, 20, 1)) / 24);
+ }
+
+ if (!preg_match('#^.\0+#s', substr($data, 47, 17))) {
+ $newData['LensID'] = static::$NikkorZLensIDS[Utils::LittleEndian2Int(substr($data, 48, 2))];
+ $newData['MaxAperture'] = pow(2, (Utils::LittleEndian2Int(substr($data, 54, 2)) / 384 - 1));
+ $newData['FNumber'] = pow(2, (Utils::LittleEndian2Int(substr($data, 56, 2)) / 384 - 1));
+ $newData['FocalLength'] = Utils::LittleEndian2Int(substr($data, 60, 2)); // mm
+ $newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 79, 1)) / 40); // m
+ }
+
+ $data = $newData;
+ break;
+ default:
+ // $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+ $data = array(
+ 'LensDataVersion' => $version,
+ );
+ break;
+ }
+ break;
+ case 0x020000a7: // ShutterCount
+ $shutterCount = $data;
+ break;
+ case 0x020000a8: // FlashInfo
+ $version = substr($data, 0, 4);
+
+ switch ($version) {
+ case '0100':
+ case '0101':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+ 'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+ 'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+ 'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+ 'FlashFocalLength' => ord(substr($data, 11, 1)), // mm
+ 'RepeatingFlashRate' => ord(substr($data, 12, 1)), // Hz
+ 'RepeatingFlashCount' => ord(substr($data, 13, 1)),
+ 'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 14, 1))],
+ 'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 15, 1)) & 0x0F],
+ 'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
+ 'FlashGroupAOutput' => (ord(substr($data, 15, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 17, 1)) / 6) * 100)) : 0,
+ 'FlashGroupACompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 17, 1), false, true) / 6),
+ 'FlashGroupBOutput' => (ord(substr($data, 16, 1)) & 0xF0) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
+ 'FlashGroupBCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
+ );
+ break;
+ case '0102':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+ 'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+ 'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+ 'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+ 'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+ 'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+ 'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+ 'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+ 'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
+ 'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0xF0],
+ 'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+ 'FlashGroupAOutput' => (ord(substr($data, 16, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
+ 'FlashGroupACompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
+ 'FlashGroupBOutput' => (ord(substr($data, 17, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
+ 'FlashGroupBCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
+ 'FlashGroupCOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
+ 'FlashGroupCCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
+ );
+ break;
+ case '0103':
+ case '0104':
+ case '0105':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+ 'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+ 'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+ 'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+ 'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+ 'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+ 'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+ 'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+ 'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
+ 'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+ 'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+ 'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+ 'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
+ 'FlashGroupACompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
+ 'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
+ 'FlashGroupBCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
+ 'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 21, 1)) / 6) * 100)) : 0,
+ 'FlashGroupCCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 21, 1), false, true) / 6),
+ 'ExternalFlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
+ 'FlashExposureComp3' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 29, 1), false, true) / 6),
+ 'FlashExposureComp4' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
+ );
+ break;
+ case '0106':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+ 'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+ 'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+ 'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+ 'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+ 'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+ 'FlashGNDistance' => self::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+ 'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
+ 'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+ 'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+ 'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+ 'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 39, 1)) / 6) * 100)) : 0,
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
+ 'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
+ 'FlashGroupACompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
+ 'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
+ 'FlashGroupBCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
+ 'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
+ 'FlashGroupCCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
+ );
+ break;
+ case '0107':
+ case '0108':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'ExternalFlashZoomOverride' => (bool)(ord(substr($data, 8, 1)) & 0x80),
+ 'ExternalFlashStatus' => static::$flashInfoExternalFlashStatuses[ord(substr($data, 8, 1)) & 0x01],
+ 'ExternalFlashReadyState' => static::$flashInfoExternalFlashReadyStates[ord(substr($data, 9, 1)) & 0x07],
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+ 'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+ 'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+ 'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+ 'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+ 'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+ 'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+ 'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+ 'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
+ 'FlashGroupACompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
+ 'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
+ 'FlashGroupBCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
+ 'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
+ 'FlashGroupCCompensation' => sprintf('%+.1f', -Utils::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
+ );
+ break;
+ case '0300':
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ 'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+ 'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+ 'FlashCompensation' => $this->printFraction(-Utils::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
+ );
+ break;
+ default:
+ $data = array(
+ 'FlashInfoVersion' => substr($data, 0, 4),
+ );
+ break;
+ }
+ break;
+ case 0x020000b1: // HighISONoiseReduction
+ $data = isset(static::$highISONoiseReductions[$data]) ? static::$highISONoiseReductions[$data] : $data;
+ break;
+ case 0x020000b7: // AFInfo2
+ $avInfo2Version = substr($data, 0, 4);
+ $contrastDetectAF = ord(substr($data, 4, 1));
+ $phaseDetectAF = ord(substr($data, 6, 1));
+ $rows = array(
+ 'AFInfo2Version' => $avInfo2Version,
+ 'ContrastDetectAF' => static::$AFInfo2ContrastDetectAFChoices[$contrastDetectAF],
+ 'AFAreaMode' => $contrastDetectAF
+ ? static::$AFInfo2AFAreaModesWithContrastDetectAF[ord(substr($data, 5, 1))]
+ : static::$AFInfo2AFAreaModesWithoutContrastDetectAF[ord(substr($data, 5, 1))],
+ 'PhaseDetectAF' => static::$AFInfo2PhaseDetectAFChoices[$phaseDetectAF],
+ );
+
+ if ($avInfo2Version === '0100') {
+ $rows['AFImageWidth'] = Utils::BigEndian2Int(substr($data, 16, 2));
+ $rows['AFImageHeight'] = Utils::BigEndian2Int(substr($data, 18, 2));
+ $rows['AFAreaXPosition'] = Utils::BigEndian2Int(substr($data, 20, 2));
+ $rows['AFAreaYPosition'] = Utils::BigEndian2Int(substr($data, 22, 2));
+ $rows['AFAreaWidth'] = Utils::BigEndian2Int(substr($data, 24, 2));
+ $rows['AFAreaHeight'] = Utils::BigEndian2Int(substr($data, 26, 2));
+ $rows['ContrastDetectAFInFocus'] = (bool)ord(substr($data, 28, 1));
+ } elseif (strpos($avInfo2Version, '03') === 0) {
+ $rows['AFImageWidth'] = Utils::BigEndian2Int(substr($data, 42, 2));
+ $rows['AFImageHeight'] = Utils::BigEndian2Int(substr($data, 44, 2));
+ if ($contrastDetectAF === 2
+ || ($contrastDetectAF === 1 && $avInfo2Version === '0301')
+ ) {
+ $rows['AFAreaXPosition'] = Utils::BigEndian2Int(substr($data, 46, 2));
+ $rows['AFAreaYPosition'] = Utils::BigEndian2Int(substr($data, 48, 2));
+ }
+ $rows['AFAreaWidth'] = Utils::BigEndian2Int(substr($data, 50, 2));
+ $rows['AFAreaHeight'] = Utils::BigEndian2Int(substr($data, 52, 2));
+ } elseif ($contrastDetectAF === 1 && $avInfo2Version === '0101') {
+ $rows['AFImageWidth'] = Utils::BigEndian2Int(substr($data, 70, 2));
+ $rows['AFImageHeight'] = Utils::BigEndian2Int(substr($data, 72, 2));
+ $rows['AFAreaXPosition'] = Utils::BigEndian2Int(substr($data, 74, 2));
+ $rows['AFAreaYPosition'] = Utils::BigEndian2Int(substr($data, 76, 2));
+ $rows['AFAreaWidth'] = Utils::BigEndian2Int(substr($data, 78, 2));
+ $rows['AFAreaHeight'] = Utils::BigEndian2Int(substr($data, 80, 2));
+ $rows['ContrastDetectAFInFocus'] = (bool) ord(substr($data, 82, 1));
+ }
+
+ $data = $rows;
+ break;
+ case 0x020000c3: // BarometerInfo
+ $data = array(
+ 'BarometerInfoVersion' => substr($data, 0, 4),
+ 'Altitude' => Utils::BigEndian2Int(substr($data, 6, 4), false, true), // m
+ );
+ break;
+ }
+ $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x' . str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
+
+ $parsed[$tag_name] = $data;
+ }
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * @param int $value 0x80 subtracted
+ * @param string $normalName 'Normal' (0 value) string
+ * @param string|null $format format string for numbers (default '%+d'), 3) v2 divisor
+ * @param int|null $div
+ *
+ * @return string
+ */
+ protected function printPC($value, $normalName = 'Normal', $format = '%+d', $div = 1) {
+ switch ($value) {
+ case 0:
+ return $normalName;
+ case 0x7f:
+ return 'n/a';
+ case -0x80:
+ return 'Auto';
+ case -0x7f:
+ return 'User';
+ }
+
+ return sprintf($format, $value / $div);
+ }
+
+ /**
+ * @param int|float $value
+ *
+ * @return string
+ */
+ protected function printFraction($value) {
+ if (!$value) {
+ return '0';
+ } elseif ((int) $value /$value > 0.999) {
+ return sprintf("%+d", (int) $value);
+ } elseif ((int) ($value * 2) / ($value * 2) > 0.999) {
+ return sprintf("%+d/2", (int) ($value * 2));
+ } elseif ((int) ($value * 3) / ($value * 3) > 0.999) {
+ return sprintf("%+d/3", (int) ($value * 3));
+ }
+
+ return sprintf("%+.3g", $value);
+ }
+
+ /**
+ * @param int $firstByte
+ * @param int $secondByte
+ *
+ * @return string
+ */
+ protected function flashFirmwareLookup($firstByte, $secondByte)
+ {
+ $indexKey = $firstByte.' '.$secondByte;
+ if (isset(static::$flashInfoExternalFlashFirmwares[$indexKey])) {
+ return static::$flashInfoExternalFlashFirmwares[$indexKey];
+ }
+
+ return sprintf('%d.%.2d (Unknown model)', $firstByte, $secondByte);
+ }
+
+ /**
+ * @param int $flags
+ *
+ * @return string[]|string
+ */
+ protected function externalFlashFlagsLookup($flags)
+ {
+ $result = array();
+ foreach (static::$flashInfoExternalFlashFlags as $bit => $value) {
+ if (($flags >> $bit) & 1) {
+ $result[] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $data
+ * @param mixed|null $serialNumber
+ * @param mixed|null $shutterCount
+ * @param int $decryptStart
+ *
+ * @return false|string
+ */
+ protected function decryptLensInfo(
+ $data,
+ $serialNumber = null,
+ $shutterCount = null,
+ $decryptStart = 0
+ ) {
+ if (null === $serialNumber && null === $shutterCount) {
+ return false;
+ }
+
+ if (!is_int($serialNumber) || !is_int($shutterCount)) {
+ if (null !== $serialNumber && null !== $shutterCount) {
+ $this->getid3->warning('Invalid '.(!is_int($serialNumber) ? 'SerialNumber' : 'ShutterCount'));
+ } else {
+ $this->getid3->warning('Cannot decrypt Nikon tags because '.(null === $serialNumber ? 'SerialNumber' : 'ShutterCount').' key is not defined.');
+ }
+
+ return false;
+ }
+
+ $start = $decryptStart;
+ $length = strlen($data) - $start;
+
+ return $this->decrypt($data, $serialNumber, $shutterCount, $start, $length);
+ }
+
+ /**
+ * Decrypt Nikon data block
+ *
+ * @param string $data
+ * @param int $serialNumber
+ * @param int $count
+ * @param int $start
+ * @param int $length
+ *
+ * @return string
+ */
+ protected function decrypt($data, $serialNumber, $count, $start = 0, $length = null)
+ {
+ $maxLen = strlen($data) - $start;
+ if (null === $length || $length > $maxLen) {
+ $length = $maxLen;
+ }
+
+ if ($length <= 0) {
+ return $data;
+ }
+
+ $key = 0;
+ for ($i = 0; $i < 4; ++$i) {
+ $key ^= ($count >> ($i * 8)) & 0xFF;
+ }
+ $ci = static::$decodeTables[0][$serialNumber & 0xff];
+ $cj = static::$decodeTables[1][$key];
+ $ck = 0x60;
+ $unpackedData = array();
+ for ($i = $start; $i < $length + $start; ++$i) {
+ $cj = ($cj + $ci * $ck) & 0xff;
+ $ck = ($ck + 1) & 0xff;
+ $unpackedData[] = ord($data[$i]) ^ $cj;
+ }
+
+ $end = $start + $length;
+ $pre = $start ? substr($data, 0, $start) : '';
+ $post = $end < strlen($data) ? substr($data, $end) : '';
+
+ return $pre . implode('', array_map('chr', $unpackedData)) . $post;
+ }
+
+ /**
+ * Get serial number for use as a decryption key
+ *
+ * @param string $serialNumber
+ * @param string|null $model
+ *
+ * @return int|null
+ */
+ protected function serialKey($serialNumber, $model = null)
+ {
+ if (empty($serialNumber) || ctype_digit($serialNumber)) {
+ return (int) $serialNumber;
+ }
+
+ if (null !== $model && preg_match('#\bD50$#', $model)) {
+ return 0x22;
+ }
+
+ return 0x60;
+ }
+}
diff --git a/src/Module/Tag/Xmp.php b/src/Module/Tag/Xmp.php
index adc2dec7..03c8f0dd 100644
--- a/src/Module/Tag/Xmp.php
+++ b/src/Module/Tag/Xmp.php
@@ -82,7 +82,7 @@ public function getAllTags()
* Reads all the JPEG header segments from an JPEG image file into an array
*
* @param string $filename - the filename of the JPEG file to read
- * @return array|boolean $headerdata - Array of JPEG header segments,
+ * @return array|false $headerdata - Array of JPEG header segments,
* FALSE - if headers could not be read
*/
public function _get_jpeg_header_data($filename)
@@ -193,8 +193,8 @@ public function _get_jpeg_header_data($filename)
* Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
*
* @param string $filename - the filename of the JPEG file to read
- * @return string|boolean $xmp_data - the string of raw XML text,
- * FALSE - if an APP 1 XMP segment could not be found, or if an error occured
+ * @return string|false $xmp_data - the string of raw XML text,
+ * FALSE - if an APP 1 XMP segment could not be found, or if an error occurred
*/
public function _get_XMP_text($filename)
{
@@ -202,22 +202,25 @@ public function _get_XMP_text($filename)
$jpeg_header_data = $this->_get_jpeg_header_data($filename);
//Cycle through the header segments
- for ($i = 0; $i < count($jpeg_header_data); $i++)
- {
- // If we find an APP1 header,
- if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
- {
- // And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
- if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
- {
- // Found a XMP/RDF block
- // Return the XMP text
- $xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
-
- return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
+ if (is_array($jpeg_header_data) && count($jpeg_header_data) > 0) {
+ foreach ($jpeg_header_data as $segment) {
+ // If we find an APP1 header,
+ if (strcmp($segment['SegName'], 'APP1') === 0) {
+ // And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
+ if (strncmp($segment['SegData'], 'http://ns.adobe.com/xap/1.0/' . "\x00", 29) === 0) {
+ // Found a XMP/RDF block
+ // Return the XMP text
+ $xmp_data = substr($segment['SegData'], 29);
+
+ // trim() should not be necessary, but some files found in the wild with null-terminated block
+ // (known samples from Apple Aperture) causes problems elsewhere
+ // (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
+ return trim($xmp_data);
+ }
}
}
}
+
return false;
}
@@ -226,7 +229,7 @@ public function _get_XMP_text($filename)
* which contains all the XMP (XML) information.
*
* @param string $xmltext - a string containing the XMP data (XML) to be parsed
- * @return array|boolean $xmp_array - an array containing all xmp details retrieved,
+ * @return array|false $xmp_array - an array containing all xmp details retrieved,
* FALSE - couldn't parse the XMP data.
*/
public function read_XMP_array_from_text($xmltext)
diff --git a/src/Utils.php b/src/Utils.php
index 34a7260b..4157d6a1 100644
--- a/src/Utils.php
+++ b/src/Utils.php
@@ -399,7 +399,7 @@ public static function LittleEndian2Float($byteword) {
/**
* ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
*
- * @link http://www.psc.edu/general/software/packages/ieee/ieee.html
+ * @link https://web.archive.org/web/20120325162206/http://www.psc.edu/general/software/packages/ieee/ieee.php
* @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
*
* @param string $byteword
@@ -451,12 +451,12 @@ public static function BigEndian2Float($byteword) {
if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
// Not a Number
- $floatvalue = false;
+ $floatvalue = NAN;
} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
if ($signbit == '1') {
- $floatvalue = '-infinity';
+ $floatvalue = -INF;
} else {
- $floatvalue = '+infinity';
+ $floatvalue = INF;
}
} elseif (($exponent == 0) && ($fraction == 0)) {
if ($signbit == '1') {
@@ -584,14 +584,20 @@ public static function BigEndian2String($number, $minbytes=1, $synchsafe=false,
* @return string
*/
public static function Dec2Bin($number) {
+ if (!is_numeric($number)) {
+ // https://github.com/JamesHeinrich/getID3/issues/299
+ trigger_error('TypeError: Dec2Bin(): Argument #1 ($number) must be numeric, '.gettype($number).' given', E_USER_WARNING);
+ return '';
+ }
+ $bytes = array();
while ($number >= 256) {
- $bytes[] = (($number / 256) - (floor($number / 256))) * 256;
+ $bytes[] = (int) (($number / 256) - (floor($number / 256))) * 256;
$number = floor($number / 256);
}
- $bytes[] = $number;
+ $bytes[] = (int) $number;
$binstring = '';
- for ($i = 0; $i < count($bytes); $i++) {
- $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
+ foreach ($bytes as $i => $byte) {
+ $binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
}
return $binstring;
}
@@ -822,6 +828,7 @@ public static function CreateDeepArray($ArrayPath, $Separator, $Value) {
// or
// $foo['path']['to']['my'] = 'file.txt';
$ArrayPath = ltrim($ArrayPath, $Separator);
+ $ReturnedArray = array();
if (($pos = strpos($ArrayPath, $Separator)) !== false) {
$ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
} else {
@@ -1663,12 +1670,21 @@ public static function ImageExtFromMime($mime_type) {
public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) {
// Copy all entries from ['tags'] into common ['comments']
if (!empty($ThisFileInfo['tags'])) {
- if (isset($ThisFileInfo['tags']['id3v1'])) {
- // bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
- $ID3v1 = $ThisFileInfo['tags']['id3v1'];
- unset($ThisFileInfo['tags']['id3v1']);
- $ThisFileInfo['tags']['id3v1'] = $ID3v1;
- unset($ID3v1);
+
+ // Some tag types can only support limited character sets and may contain data in non-standard encoding (usually ID3v1)
+ // and/or poorly-transliterated tag values that are also in tag formats that do support full-range character sets
+ // To make the output more user-friendly, process the potentially-problematic tag formats last to enhance the chance that
+ // the first entries in [comments] are the most correct and the "bad" ones (if any) come later.
+ // https://github.com/JamesHeinrich/getID3/issues/338
+ $processLastTagTypes = array('id3v1','riff');
+ foreach ($processLastTagTypes as $processLastTagType) {
+ if (isset($ThisFileInfo['tags'][$processLastTagType])) {
+ // bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
+ $temp = $ThisFileInfo['tags'][$processLastTagType];
+ unset($ThisFileInfo['tags'][$processLastTagType]);
+ $ThisFileInfo['tags'][$processLastTagType] = $temp;
+ unset($temp);
+ }
}
foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
foreach ($tagarray as $tagname => $tagdata) {
@@ -1699,9 +1715,18 @@ public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true
} elseif (!is_array($value)) {
- $newvaluelength = strlen(trim($value));
+ $newvaluelength = strlen(trim($value));
+ $newvaluelengthMB = mb_strlen(trim($value));
foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
- $oldvaluelength = strlen(trim($existingvalue));
+ $oldvaluelength = strlen(trim($existingvalue));
+ $oldvaluelengthMB = mb_strlen(trim($existingvalue));
+ if (($newvaluelengthMB == $oldvaluelengthMB) && ($existingvalue == Utils::iconv_fallback('UTF-8', 'ASCII', $value))) {
+ // https://github.com/JamesHeinrich/getID3/issues/338
+ // check for tags containing extended characters that may have been forced into limited-character storage (e.g. UTF8 values into ASCII)
+ // which will usually display unrepresentable characters as "?"
+ $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
+ break;
+ }
if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
break;
@@ -1834,6 +1859,7 @@ public static function trimNullByte($string) {
* @return float|bool
*/
public static function getFileSizeSyscall($path) {
+ $commandline = null;
$filesize = false;
if (static::isWindows()) {
@@ -1911,7 +1937,7 @@ public static function truepath($filename) {
*
* @return string
*/
- public static function mb_basename($path, $suffix = null) {
+ public static function mb_basename($path, $suffix = '') {
$splited = preg_split('#/#', rtrim($path, '/ '));
return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1);
}
diff --git a/src/Write/ID3v2.php b/src/Write/ID3v2.php
index eccaafb6..03cfdf88 100644
--- a/src/Write/ID3v2.php
+++ b/src/Write/ID3v2.php
@@ -1981,8 +1981,8 @@ public function is_hash($var) {
if (is_array($var)) {
$keys = array_keys($var);
$all_num = true;
- for ($i = 0; $i < count($keys); $i++) {
- if (is_string($keys[$i])) {
+ foreach ($keys as $key) {
+ if (is_string($key)) {
return true;
}
}
diff --git a/src/Write/Real.php b/src/Write/Real.php
index 199e1075..1fe0cbb9 100644
--- a/src/Write/Real.php
+++ b/src/Write/Real.php
@@ -283,6 +283,7 @@ public function RemoveReal() {
fclose($fp_source);
return false;
}
+ $oldChunkInfo = array();
foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
$oldChunkInfo[$chunkarray['name']] = $chunkarray;
}
diff --git a/src/WriteTags.php b/src/WriteTags.php
index 7f90ac66..03d4677d 100644
--- a/src/WriteTags.php
+++ b/src/WriteTags.php
@@ -558,6 +558,7 @@ public function FormatDataForID3v1() {
public function FormatDataForID3v2($id3v2_majorversion) {
$tag_data_id3v2 = array();
+ $ID3v2_text_encoding_lookup = array();
$ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
$ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
$ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
@@ -736,6 +737,7 @@ public function FormatDataForMetaFLAC() {
* @return array
*/
public function FormatDataForReal() {
+ $tag_data_real = array();
$tag_data_real['title'] = Utils::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
$tag_data_real['artist'] = Utils::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
$tag_data_real['copyright'] = Utils::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array())));