[ Index ]

WordPress Cross Reference

title

Body

[close]

/wp-includes/ID3/ -> module.tag.apetag.php (source)

   1  <?php
   2  /////////////////////////////////////////////////////////////////
   3  /// getID3() by James Heinrich <info@getid3.org>               //
   4  //  available at http://getid3.sourceforge.net                 //
   5  //            or http://www.getid3.org                         //
   6  /////////////////////////////////////////////////////////////////
   7  // See readme.txt for more details                             //
   8  /////////////////////////////////////////////////////////////////
   9  //                                                             //
  10  // module.tag.apetag.php                                       //
  11  // module for analyzing APE tags                               //
  12  // dependencies: NONE                                          //
  13  //                                                            ///
  14  /////////////////////////////////////////////////////////////////
  15  
  16  class getid3_apetag extends getid3_handler
  17  {
  18      public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
  19      public $overrideendoffset  = 0;
  20  
  21  	public function Analyze() {
  22          $info = &$this->getid3->info;
  23  
  24          if (!getid3_lib::intValueSupported($info['filesize'])) {
  25              $info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
  26              return false;
  27          }
  28  
  29          $id3v1tagsize     = 128;
  30          $apetagheadersize = 32;
  31          $lyrics3tagsize   = 10;
  32  
  33          if ($this->overrideendoffset == 0) {
  34  
  35              fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
  36              $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
  37  
  38              //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
  39              if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
  40  
  41                  // APE tag found before ID3v1
  42                  $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
  43  
  44              //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
  45              } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
  46  
  47                  // APE tag found, no ID3v1
  48                  $info['ape']['tag_offset_end'] = $info['filesize'];
  49  
  50              }
  51  
  52          } else {
  53  
  54              fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
  55              if (fread($this->getid3->fp, 8) == 'APETAGEX') {
  56                  $info['ape']['tag_offset_end'] = $this->overrideendoffset;
  57              }
  58  
  59          }
  60          if (!isset($info['ape']['tag_offset_end'])) {
  61  
  62              // APE tag not found
  63              unset($info['ape']);
  64              return false;
  65  
  66          }
  67  
  68          // shortcut
  69          $thisfile_ape = &$info['ape'];
  70  
  71          fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
  72          $APEfooterData = fread($this->getid3->fp, 32);
  73          if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
  74              $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
  75              return false;
  76          }
  77  
  78          if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
  79              fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
  80              $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
  81              $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
  82          } else {
  83              $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
  84              fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
  85              $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
  86          }
  87          $info['avdataend'] = $thisfile_ape['tag_offset_start'];
  88  
  89          if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
  90              $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
  91              unset($info['id3v1']);
  92              foreach ($info['warning'] as $key => $value) {
  93                  if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
  94                      unset($info['warning'][$key]);
  95                      sort($info['warning']);
  96                      break;
  97                  }
  98              }
  99          }
 100  
 101          $offset = 0;
 102          if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
 103              if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
 104                  $offset += $apetagheadersize;
 105              } else {
 106                  $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
 107                  return false;
 108              }
 109          }
 110  
 111          // shortcut
 112          $info['replay_gain'] = array();
 113          $thisfile_replaygain = &$info['replay_gain'];
 114  
 115          for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
 116              $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
 117              $offset += 4;
 118              $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
 119              $offset += 4;
 120              if (strstr(substr($APEtagData, $offset), "\x00") === false) {
 121                  $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                  return false;
 123              }
 124              $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
 125              $item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
 126  
 127              // shortcut
 128              $thisfile_ape['items'][$item_key] = array();
 129              $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
 130  
 131              $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
 132  
 133              $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
 134              $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
 135              $offset += $value_size;
 136  
 137              $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
 138              switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
 139                  case 0: // UTF-8
 140                  case 3: // Locator (URL, filename, etc), UTF-8 encoded
 141                      $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
 142                      break;
 143  
 144                  default: // binary data
 145                      break;
 146              }
 147  
 148              switch (strtolower($item_key)) {
 149                  case 'replaygain_track_gain':
 150                      $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 151                      $thisfile_replaygain['track']['originator'] = 'unspecified';
 152                      break;
 153  
 154                  case 'replaygain_track_peak':
 155                      $thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 156                      $thisfile_replaygain['track']['originator'] = 'unspecified';
 157                      if ($thisfile_replaygain['track']['peak'] <= 0) {
 158                          $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
 159                      }
 160                      break;
 161  
 162                  case 'replaygain_album_gain':
 163                      $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 164                      $thisfile_replaygain['album']['originator'] = 'unspecified';
 165                      break;
 166  
 167                  case 'replaygain_album_peak':
 168                      $thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 169                      $thisfile_replaygain['album']['originator'] = 'unspecified';
 170                      if ($thisfile_replaygain['album']['peak'] <= 0) {
 171                          $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
 172                      }
 173                      break;
 174  
 175                  case 'mp3gain_undo':
 176                      list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
 177                      $thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
 178                      $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
 179                      $thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
 180                      break;
 181  
 182                  case 'mp3gain_minmax':
 183                      list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
 184                      $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
 185                      $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
 186                      break;
 187  
 188                  case 'mp3gain_album_minmax':
 189                      list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
 190                      $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
 191                      $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
 192                      break;
 193  
 194                  case 'tracknumber':
 195                      if (is_array($thisfile_ape_items_current['data'])) {
 196                          foreach ($thisfile_ape_items_current['data'] as $comment) {
 197                              $thisfile_ape['comments']['track'][] = $comment;
 198                          }
 199                      }
 200                      break;
 201  
 202                  case 'cover art (artist)':
 203                  case 'cover art (back)':
 204                  case 'cover art (band logo)':
 205                  case 'cover art (band)':
 206                  case 'cover art (colored fish)':
 207                  case 'cover art (composer)':
 208                  case 'cover art (conductor)':
 209                  case 'cover art (front)':
 210                  case 'cover art (icon)':
 211                  case 'cover art (illustration)':
 212                  case 'cover art (lead)':
 213                  case 'cover art (leaflet)':
 214                  case 'cover art (lyricist)':
 215                  case 'cover art (media)':
 216                  case 'cover art (movie scene)':
 217                  case 'cover art (other icon)':
 218                  case 'cover art (other)':
 219                  case 'cover art (performance)':
 220                  case 'cover art (publisher logo)':
 221                  case 'cover art (recording)':
 222                  case 'cover art (studio)':
 223                      // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
 224                      list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
 225                      $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
 226                      $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
 227  
 228                      $thisfile_ape_items_current['image_mime'] = '';
 229                      $imageinfo = array();
 230                      $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
 231                      $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
 232  
 233                      do {
 234                          if ($this->inline_attachments === false) {
 235                              // skip entirely
 236                              unset($thisfile_ape_items_current['data']);
 237                              break;
 238                          }
 239                          if ($this->inline_attachments === true) {
 240                              // great
 241                          } elseif (is_int($this->inline_attachments)) {
 242                              if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
 243                                  // too big, skip
 244                                  $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
 245                                  unset($thisfile_ape_items_current['data']);
 246                                  break;
 247                              }
 248                          } elseif (is_string($this->inline_attachments)) {
 249                              $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
 250                              if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
 251                                  // cannot write, skip
 252                                  $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
 253                                  unset($thisfile_ape_items_current['data']);
 254                                  break;
 255                              }
 256                          }
 257                          // if we get this far, must be OK
 258                          if (is_string($this->inline_attachments)) {
 259                              $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
 260                              if (!file_exists($destination_filename) || is_writable($destination_filename)) {
 261                                  file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
 262                              } else {
 263                                  $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
 264                              }
 265                              $thisfile_ape_items_current['data_filename'] = $destination_filename;
 266                              unset($thisfile_ape_items_current['data']);
 267                          } else {
 268                              if (!isset($info['ape']['comments']['picture'])) {
 269                                  $info['ape']['comments']['picture'] = array();
 270                              }
 271                              $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
 272                          }
 273                      } while (false);
 274                      break;
 275  
 276                  default:
 277                      if (is_array($thisfile_ape_items_current['data'])) {
 278                          foreach ($thisfile_ape_items_current['data'] as $comment) {
 279                              $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
 280                          }
 281                      }
 282                      break;
 283              }
 284  
 285          }
 286          if (empty($thisfile_replaygain)) {
 287              unset($info['replay_gain']);
 288          }
 289          return true;
 290      }
 291  
 292  	public function parseAPEheaderFooter($APEheaderFooterData) {
 293          // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
 294  
 295          // shortcut
 296          $headerfooterinfo['raw'] = array();
 297          $headerfooterinfo_raw = &$headerfooterinfo['raw'];
 298  
 299          $headerfooterinfo_raw['footer_tag']   =                  substr($APEheaderFooterData,  0, 8);
 300          if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
 301              return false;
 302          }
 303          $headerfooterinfo_raw['version']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData,  8, 4));
 304          $headerfooterinfo_raw['tagsize']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
 305          $headerfooterinfo_raw['tag_items']    = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
 306          $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
 307          $headerfooterinfo_raw['reserved']     =                              substr($APEheaderFooterData, 24, 8);
 308  
 309          $headerfooterinfo['tag_version']         = $headerfooterinfo_raw['version'] / 1000;
 310          if ($headerfooterinfo['tag_version'] >= 2) {
 311              $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
 312          }
 313          return $headerfooterinfo;
 314      }
 315  
 316  	public function parseAPEtagFlags($rawflagint) {
 317          // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
 318          // All are set to zero on creation and ignored on reading."
 319          // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
 320          $flags['header']            = (bool) ($rawflagint & 0x80000000);
 321          $flags['footer']            = (bool) ($rawflagint & 0x40000000);
 322          $flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
 323          $flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
 324          $flags['read_only']         = (bool) ($rawflagint & 0x00000001);
 325  
 326          $flags['item_contents']     = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
 327  
 328          return $flags;
 329      }
 330  
 331  	public function APEcontentTypeFlagLookup($contenttypeid) {
 332          static $APEcontentTypeFlagLookup = array(
 333              0 => 'utf-8',
 334              1 => 'binary',
 335              2 => 'external',
 336              3 => 'reserved'
 337          );
 338          return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
 339      }
 340  
 341  	public function APEtagItemIsUTF8Lookup($itemkey) {
 342          static $APEtagItemIsUTF8Lookup = array(
 343              'title',
 344              'subtitle',
 345              'artist',
 346              'album',
 347              'debut album',
 348              'publisher',
 349              'conductor',
 350              'track',
 351              'composer',
 352              'comment',
 353              'copyright',
 354              'publicationright',
 355              'file',
 356              'year',
 357              'record date',
 358              'record location',
 359              'genre',
 360              'media',
 361              'related',
 362              'isrc',
 363              'abstract',
 364              'language',
 365              'bibliography'
 366          );
 367          return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
 368      }
 369  
 370  }


Generated: Tue Mar 25 01:41:18 2014 WordPress honlapkészítés: online1.hu