wp/wp-includes/ID3/module.tag.apetag.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 16 a86126ab1dd4
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    21 
    21 
    22 	public function Analyze() {
    22 	public function Analyze() {
    23 		$info = &$this->getid3->info;
    23 		$info = &$this->getid3->info;
    24 
    24 
    25 		if (!getid3_lib::intValueSupported($info['filesize'])) {
    25 		if (!getid3_lib::intValueSupported($info['filesize'])) {
    26 			$info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
    26 			$this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
    27 			return false;
    27 			return false;
    28 		}
    28 		}
    29 
    29 
    30 		$id3v1tagsize     = 128;
    30 		$id3v1tagsize     = 128;
    31 		$apetagheadersize = 32;
    31 		$apetagheadersize = 32;
    70 		$thisfile_ape = &$info['ape'];
    70 		$thisfile_ape = &$info['ape'];
    71 
    71 
    72 		$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
    72 		$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
    73 		$APEfooterData = $this->fread(32);
    73 		$APEfooterData = $this->fread(32);
    74 		if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
    74 		if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
    75 			$info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
    75 			$this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
    76 			return false;
    76 			return false;
    77 		}
    77 		}
    78 
    78 
    79 		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
    79 		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
    80 			$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
    80 			$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
    86 			$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
    86 			$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
    87 		}
    87 		}
    88 		$info['avdataend'] = $thisfile_ape['tag_offset_start'];
    88 		$info['avdataend'] = $thisfile_ape['tag_offset_start'];
    89 
    89 
    90 		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
    90 		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
    91 			$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
    91 			$this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
    92 			unset($info['id3v1']);
    92 			unset($info['id3v1']);
    93 			foreach ($info['warning'] as $key => $value) {
    93 			foreach ($info['warning'] as $key => $value) {
    94 				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
    94 				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
    95 					unset($info['warning'][$key]);
    95 					unset($info['warning'][$key]);
    96 					sort($info['warning']);
    96 					sort($info['warning']);
   102 		$offset = 0;
   102 		$offset = 0;
   103 		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
   103 		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
   104 			if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
   104 			if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
   105 				$offset += $apetagheadersize;
   105 				$offset += $apetagheadersize;
   106 			} else {
   106 			} else {
   107 				$info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
   107 				$this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
   108 				return false;
   108 				return false;
   109 			}
   109 			}
   110 		}
   110 		}
   111 
   111 
   112 		// shortcut
   112 		// shortcut
   117 			$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
   117 			$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
   118 			$offset += 4;
   118 			$offset += 4;
   119 			$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
   119 			$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
   120 			$offset += 4;
   120 			$offset += 4;
   121 			if (strstr(substr($APEtagData, $offset), "\x00") === false) {
   121 			if (strstr(substr($APEtagData, $offset), "\x00") === false) {
   122 				$info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
   122 				$this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
   123 				return false;
   123 				return false;
   124 			}
   124 			}
   125 			$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
   125 			$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
   126 			$item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
   126 			$item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
   127 
   127 
   136 			$offset += $value_size;
   136 			$offset += $value_size;
   137 
   137 
   138 			$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
   138 			$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
   139 			switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
   139 			switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
   140 				case 0: // UTF-8
   140 				case 0: // UTF-8
   141 				case 3: // Locator (URL, filename, etc), UTF-8 encoded
   141 				case 2: // Locator (URL, filename, etc), UTF-8 encoded
   142 					$thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
   142 					$thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']);
   143 					break;
   143 					break;
   144 
   144 
   145 				default: // binary data
   145 				case 1:  // binary data
       
   146 				default:
   146 					break;
   147 					break;
   147 			}
   148 			}
   148 
   149 
   149 			switch (strtolower($item_key)) {
   150 			switch (strtolower($item_key)) {
       
   151 				// http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
   150 				case 'replaygain_track_gain':
   152 				case 'replaygain_track_gain':
   151 					$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   153 					if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
   152 					$thisfile_replaygain['track']['originator'] = 'unspecified';
   154 						$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
       
   155 						$thisfile_replaygain['track']['originator'] = 'unspecified';
       
   156 					} else {
       
   157 						$this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
       
   158 					}
   153 					break;
   159 					break;
   154 
   160 
   155 				case 'replaygain_track_peak':
   161 				case 'replaygain_track_peak':
   156 					$thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   162 					if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
   157 					$thisfile_replaygain['track']['originator'] = 'unspecified';
   163 						$thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   158 					if ($thisfile_replaygain['track']['peak'] <= 0) {
   164 						$thisfile_replaygain['track']['originator'] = 'unspecified';
   159 						$info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
   165 						if ($thisfile_replaygain['track']['peak'] <= 0) {
       
   166 							$this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
       
   167 						}
       
   168 					} else {
       
   169 						$this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
   160 					}
   170 					}
   161 					break;
   171 					break;
   162 
   172 
   163 				case 'replaygain_album_gain':
   173 				case 'replaygain_album_gain':
   164 					$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   174 					if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
   165 					$thisfile_replaygain['album']['originator'] = 'unspecified';
   175 						$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
       
   176 						$thisfile_replaygain['album']['originator'] = 'unspecified';
       
   177 					} else {
       
   178 						$this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
       
   179 					}
   166 					break;
   180 					break;
   167 
   181 
   168 				case 'replaygain_album_peak':
   182 				case 'replaygain_album_peak':
   169 					$thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   183 					if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
   170 					$thisfile_replaygain['album']['originator'] = 'unspecified';
   184 						$thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
   171 					if ($thisfile_replaygain['album']['peak'] <= 0) {
   185 						$thisfile_replaygain['album']['originator'] = 'unspecified';
   172 						$info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
   186 						if ($thisfile_replaygain['album']['peak'] <= 0) {
       
   187 							$this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
       
   188 						}
       
   189 					} else {
       
   190 						$this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
   173 					}
   191 					}
   174 					break;
   192 					break;
   175 
   193 
   176 				case 'mp3gain_undo':
   194 				case 'mp3gain_undo':
   177 					list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
   195 					if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
   178 					$thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
   196 						list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
   179 					$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
   197 						$thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
   180 					$thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
   198 						$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
       
   199 						$thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
       
   200 					} else {
       
   201 						$this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
       
   202 					}
   181 					break;
   203 					break;
   182 
   204 
   183 				case 'mp3gain_minmax':
   205 				case 'mp3gain_minmax':
   184 					list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
   206 					if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
   185 					$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
   207 						list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
   186 					$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
   208 						$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
       
   209 						$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
       
   210 					} else {
       
   211 						$this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
       
   212 					}
   187 					break;
   213 					break;
   188 
   214 
   189 				case 'mp3gain_album_minmax':
   215 				case 'mp3gain_album_minmax':
   190 					list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
   216 					if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
   191 					$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
   217 						list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
   192 					$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
   218 						$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
       
   219 						$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
       
   220 					} else {
       
   221 						$this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
       
   222 					}
   193 					break;
   223 					break;
   194 
   224 
   195 				case 'tracknumber':
   225 				case 'tracknumber':
   196 					if (is_array($thisfile_ape_items_current['data'])) {
   226 					if (is_array($thisfile_ape_items_current['data'])) {
   197 						foreach ($thisfile_ape_items_current['data'] as $comment) {
   227 						foreach ($thisfile_ape_items_current['data'] as $comment) {
   220 				case 'cover art (performance)':
   250 				case 'cover art (performance)':
   221 				case 'cover art (publisher logo)':
   251 				case 'cover art (publisher logo)':
   222 				case 'cover art (recording)':
   252 				case 'cover art (recording)':
   223 				case 'cover art (studio)':
   253 				case 'cover art (studio)':
   224 					// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
   254 					// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
       
   255 					if (is_array($thisfile_ape_items_current['data'])) {
       
   256 						$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
       
   257 						$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
       
   258 					}
   225 					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
   259 					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
   226 					$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
   260 					$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
   227 					$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
   261 					$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
   228 
   262 
   229 					$thisfile_ape_items_current['image_mime'] = '';
       
   230 					$imageinfo = array();
       
   231 					$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
       
   232 					$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
       
   233 
       
   234 					do {
   263 					do {
       
   264 						$thisfile_ape_items_current['image_mime'] = '';
       
   265 						$imageinfo = array();
       
   266 						$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
       
   267 						if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
       
   268 							$this->warning('APEtag "'.$item_key.'" contains invalid image data');
       
   269 							break;
       
   270 						}
       
   271 						$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
       
   272 
   235 						if ($this->inline_attachments === false) {
   273 						if ($this->inline_attachments === false) {
   236 							// skip entirely
   274 							// skip entirely
   237 							unset($thisfile_ape_items_current['data']);
   275 							unset($thisfile_ape_items_current['data']);
   238 							break;
   276 							break;
   239 						}
   277 						}
   240 						if ($this->inline_attachments === true) {
   278 						if ($this->inline_attachments === true) {
   241 							// great
   279 							// great
   242 						} elseif (is_int($this->inline_attachments)) {
   280 						} elseif (is_int($this->inline_attachments)) {
   243 							if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
   281 							if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
   244 								// too big, skip
   282 								// too big, skip
   245 								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
   283 								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
   246 								unset($thisfile_ape_items_current['data']);
   284 								unset($thisfile_ape_items_current['data']);
   247 								break;
   285 								break;
   248 							}
   286 							}
   249 						} elseif (is_string($this->inline_attachments)) {
   287 						} elseif (is_string($this->inline_attachments)) {
   250 							$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
   288 							$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
   251 							if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
   289 							if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) {
   252 								// cannot write, skip
   290 								// cannot write, skip
   253 								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
   291 								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
   254 								unset($thisfile_ape_items_current['data']);
   292 								unset($thisfile_ape_items_current['data']);
   255 								break;
   293 								break;
   256 							}
   294 							}
   257 						}
   295 						}
   258 						// if we get this far, must be OK
   296 						// if we get this far, must be OK
   259 						if (is_string($this->inline_attachments)) {
   297 						if (is_string($this->inline_attachments)) {
   260 							$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
   298 							$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
   261 							if (!file_exists($destination_filename) || is_writable($destination_filename)) {
   299 							if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
   262 								file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
   300 								file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
   263 							} else {
   301 							} else {
   264 								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
   302 								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
   265 							}
   303 							}
   266 							$thisfile_ape_items_current['data_filename'] = $destination_filename;
   304 							$thisfile_ape_items_current['data_filename'] = $destination_filename;
   267 							unset($thisfile_ape_items_current['data']);
   305 							unset($thisfile_ape_items_current['data']);
   268 						} else {
   306 						} else {
   269 							if (!isset($info['ape']['comments']['picture'])) {
   307 							if (!isset($info['ape']['comments']['picture'])) {
   270 								$info['ape']['comments']['picture'] = array();
   308 								$info['ape']['comments']['picture'] = array();
   271 							}
   309 							}
   272 							$info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
   310 							$comments_picture_data = array();
       
   311 							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
       
   312 								if (isset($thisfile_ape_items_current[$picture_key])) {
       
   313 									$comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
       
   314 								}
       
   315 							}
       
   316 							$info['ape']['comments']['picture'][] = $comments_picture_data;
       
   317 							unset($comments_picture_data);
   273 						}
   318 						}
   274 					} while (false);
   319 					} while (false);
   275 					break;
   320 					break;
   276 
   321 
   277 				default:
   322 				default:
   315 	}
   360 	}
   316 
   361 
   317 	public function parseAPEtagFlags($rawflagint) {
   362 	public function parseAPEtagFlags($rawflagint) {
   318 		// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
   363 		// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
   319 		// All are set to zero on creation and ignored on reading."
   364 		// All are set to zero on creation and ignored on reading."
   320 		// http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
   365 		// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
   321 		$flags['header']            = (bool) ($rawflagint & 0x80000000);
   366 		$flags['header']            = (bool) ($rawflagint & 0x80000000);
   322 		$flags['footer']            = (bool) ($rawflagint & 0x40000000);
   367 		$flags['footer']            = (bool) ($rawflagint & 0x40000000);
   323 		$flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
   368 		$flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
   324 		$flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
   369 		$flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
   325 		$flags['read_only']         = (bool) ($rawflagint & 0x00000001);
   370 		$flags['read_only']         = (bool) ($rawflagint & 0x00000001);