[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  
   3  /////////////////////////////////////////////////////////////////
   4  /// getID3() by James Heinrich <info@getid3.org>               //
   5  //  available at https://github.com/JamesHeinrich/getID3       //
   6  //            or https://www.getid3.org                        //
   7  //            or http://getid3.sourceforge.net                 //
   8  //  see readme.txt for more details                            //
   9  /////////////////////////////////////////////////////////////////
  10  ///                                                            //
  11  // module.tag.id3v2.php                                        //
  12  // module for analyzing ID3v2 tags                             //
  13  // dependencies: module.tag.id3v1.php                          //
  14  //                                                            ///
  15  /////////////////////////////////////////////////////////////////
  16  
  17  getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
  18  
  19  class getid3_id3v2 extends getid3_handler
  20  {
  21      public $StartingOffset = 0;
  22  
  23      /**
  24       * @return bool
  25       */
  26  	public function Analyze() {
  27          $info = &$this->getid3->info;
  28  
  29          //    Overall tag structure:
  30          //        +-----------------------------+
  31          //        |      Header (10 bytes)      |
  32          //        +-----------------------------+
  33          //        |       Extended Header       |
  34          //        | (variable length, OPTIONAL) |
  35          //        +-----------------------------+
  36          //        |   Frames (variable length)  |
  37          //        +-----------------------------+
  38          //        |           Padding           |
  39          //        | (variable length, OPTIONAL) |
  40          //        +-----------------------------+
  41          //        | Footer (10 bytes, OPTIONAL) |
  42          //        +-----------------------------+
  43  
  44          //    Header
  45          //        ID3v2/file identifier      "ID3"
  46          //        ID3v2 version              $04 00
  47          //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
  48          //        ID3v2 size             4 * %0xxxxxxx
  49  
  50  
  51          // shortcuts
  52          $info['id3v2']['header'] = true;
  53          $thisfile_id3v2                  = &$info['id3v2'];
  54          $thisfile_id3v2['flags']         =  array();
  55          $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
  56  
  57  
  58          $this->fseek($this->StartingOffset);
  59          $header = $this->fread(10);
  60          if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
  61  
  62              $thisfile_id3v2['majorversion'] = ord($header[3]);
  63              $thisfile_id3v2['minorversion'] = ord($header[4]);
  64  
  65              // shortcut
  66              $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
  67  
  68          } else {
  69  
  70              unset($info['id3v2']);
  71              return false;
  72  
  73          }
  74  
  75          if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
  76  
  77              $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
  78              return false;
  79  
  80          }
  81  
  82          $id3_flags = ord($header[5]);
  83          switch ($id3v2_majorversion) {
  84              case 2:
  85                  // %ab000000 in v2.2
  86                  $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
  87                  $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
  88                  break;
  89  
  90              case 3:
  91                  // %abc00000 in v2.3
  92                  $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
  93                  $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
  94                  $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
  95                  break;
  96  
  97              case 4:
  98                  // %abcd0000 in v2.4
  99                  $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 100                  $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
 101                  $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
 102                  $thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
 103                  break;
 104          }
 105  
 106          $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
 107  
 108          $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
 109          $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
 110  
 111  
 112  
 113          // create 'encoding' key - used by getid3::HandleAllTags()
 114          // in ID3v2 every field can have it's own encoding type
 115          // so force everything to UTF-8 so it can be handled consistantly
 116          $thisfile_id3v2['encoding'] = 'UTF-8';
 117  
 118  
 119      //    Frames
 120  
 121      //        All ID3v2 frames consists of one frame header followed by one or more
 122      //        fields containing the actual information. The header is always 10
 123      //        bytes and laid out as follows:
 124      //
 125      //        Frame ID      $xx xx xx xx  (four characters)
 126      //        Size      4 * %0xxxxxxx
 127      //        Flags         $xx xx
 128  
 129          $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
 130          if (!empty($thisfile_id3v2['exthead']['length'])) {
 131              $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
 132          }
 133          if (!empty($thisfile_id3v2_flags['isfooter'])) {
 134              $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
 135          }
 136          if ($sizeofframes > 0) {
 137  
 138              $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
 139  
 140              //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
 141              if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
 142                  $framedata = $this->DeUnsynchronise($framedata);
 143              }
 144              //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
 145              //        of on tag level, making it easier to skip frames, increasing the streamability
 146              //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
 147              //        there exists an unsynchronised frame, while the new unsynchronisation flag in
 148              //        the frame header [S:4.1.2] indicates unsynchronisation.
 149  
 150  
 151              //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
 152              $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
 153  
 154  
 155              //    Extended Header
 156              if (!empty($thisfile_id3v2_flags['exthead'])) {
 157                  $extended_header_offset = 0;
 158  
 159                  if ($id3v2_majorversion == 3) {
 160  
 161                      // v2.3 definition:
 162                      //Extended header size  $xx xx xx xx   // 32-bit integer
 163                      //Extended Flags        $xx xx
 164                      //     %x0000000 %00000000 // v2.3
 165                      //     x - CRC data present
 166                      //Size of padding       $xx xx xx xx
 167  
 168                      $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
 169                      $extended_header_offset += 4;
 170  
 171                      $thisfile_id3v2['exthead']['flag_bytes'] = 2;
 172                      $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
 173                      $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
 174  
 175                      $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
 176  
 177                      $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
 178                      $extended_header_offset += 4;
 179  
 180                      if ($thisfile_id3v2['exthead']['flags']['crc']) {
 181                          $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
 182                          $extended_header_offset += 4;
 183                      }
 184                      $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
 185  
 186                  } elseif ($id3v2_majorversion == 4) {
 187  
 188                      // v2.4 definition:
 189                      //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
 190                      //Number of flag bytes       $01
 191                      //Extended Flags             $xx
 192                      //     %0bcd0000 // v2.4
 193                      //     b - Tag is an update
 194                      //         Flag data length       $00
 195                      //     c - CRC data present
 196                      //         Flag data length       $05
 197                      //         Total frame CRC    5 * %0xxxxxxx
 198                      //     d - Tag restrictions
 199                      //         Flag data length       $01
 200  
 201                      $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
 202                      $extended_header_offset += 4;
 203  
 204                      $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
 205                      $extended_header_offset += 1;
 206  
 207                      $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
 208                      $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
 209  
 210                      $thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
 211                      $thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
 212                      $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
 213  
 214                      if ($thisfile_id3v2['exthead']['flags']['update']) {
 215                          $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
 216                          $extended_header_offset += 1;
 217                      }
 218  
 219                      if ($thisfile_id3v2['exthead']['flags']['crc']) {
 220                          $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
 221                          $extended_header_offset += 1;
 222                          $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
 223                          $extended_header_offset += $ext_header_chunk_length;
 224                      }
 225  
 226                      if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
 227                          $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
 228                          $extended_header_offset += 1;
 229  
 230                          // %ppqrrstt
 231                          $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
 232                          $extended_header_offset += 1;
 233                          $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
 234                          $thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
 235                          $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
 236                          $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
 237                          $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
 238  
 239                          $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
 240                          $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
 241                          $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
 242                          $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
 243                          $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
 244                      }
 245  
 246                      if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
 247                          $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
 248                      }
 249                  }
 250  
 251                  $framedataoffset += $extended_header_offset;
 252                  $framedata = substr($framedata, $extended_header_offset);
 253              } // end extended header
 254  
 255  
 256              while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
 257                  if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
 258                      // insufficient room left in ID3v2 header for actual data - must be padding
 259                      $thisfile_id3v2['padding']['start']  = $framedataoffset;
 260                      $thisfile_id3v2['padding']['length'] = strlen($framedata);
 261                      $thisfile_id3v2['padding']['valid']  = true;
 262                      for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
 263                          if ($framedata[$i] != "\x00") {
 264                              $thisfile_id3v2['padding']['valid'] = false;
 265                              $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
 266                              $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
 267                              break;
 268                          }
 269                      }
 270                      break; // skip rest of ID3v2 header
 271                  }
 272                  $frame_header = null;
 273                  $frame_name   = null;
 274                  $frame_size   = null;
 275                  $frame_flags  = null;
 276                  if ($id3v2_majorversion == 2) {
 277                      // Frame ID  $xx xx xx (three characters)
 278                      // Size      $xx xx xx (24-bit integer)
 279                      // Flags     $xx xx
 280  
 281                      $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
 282                      $framedata    = substr($framedata, 6);    // and leave the rest in $framedata
 283                      $frame_name   = substr($frame_header, 0, 3);
 284                      $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
 285                      $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
 286  
 287                  } elseif ($id3v2_majorversion > 2) {
 288  
 289                      // Frame ID  $xx xx xx xx (four characters)
 290                      // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
 291                      // Flags     $xx xx
 292  
 293                      $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
 294                      $framedata    = substr($framedata, 10);    // and leave the rest in $framedata
 295  
 296                      $frame_name = substr($frame_header, 0, 4);
 297                      if ($id3v2_majorversion == 3) {
 298                          $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
 299                      } else { // ID3v2.4+
 300                          $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
 301                      }
 302  
 303                      if ($frame_size < (strlen($framedata) + 4)) {
 304                          $nextFrameID = substr($framedata, $frame_size, 4);
 305                          if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
 306                              // next frame is OK
 307                          } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
 308                              // MP3ext known broken frames - "ok" for the purposes of this test
 309                          } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
 310                              $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
 311                              $id3v2_majorversion = 3;
 312                              $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
 313                          }
 314                      }
 315  
 316  
 317                      $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
 318                  }
 319  
 320                  if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
 321                      // padding encountered
 322  
 323                      $thisfile_id3v2['padding']['start']  = $framedataoffset;
 324                      $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
 325                      $thisfile_id3v2['padding']['valid']  = true;
 326  
 327                      $len = strlen($framedata);
 328                      for ($i = 0; $i < $len; $i++) {
 329                          if ($framedata[$i] != "\x00") {
 330                              $thisfile_id3v2['padding']['valid'] = false;
 331                              $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
 332                              $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
 333                              break;
 334                          }
 335                      }
 336                      break; // skip rest of ID3v2 header
 337                  }
 338  
 339                  if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
 340                      $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
 341                      $frame_name = $iTunesBrokenFrameNameFixed;
 342                  }
 343                  if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
 344  
 345                      unset($parsedFrame);
 346                      $parsedFrame['frame_name']      = $frame_name;
 347                      $parsedFrame['frame_flags_raw'] = $frame_flags;
 348                      $parsedFrame['data']            = substr($framedata, 0, $frame_size);
 349                      $parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
 350                      $parsedFrame['dataoffset']      = $framedataoffset;
 351  
 352                      $this->ParseID3v2Frame($parsedFrame);
 353                      $thisfile_id3v2[$frame_name][] = $parsedFrame;
 354  
 355                      $framedata = substr($framedata, $frame_size);
 356  
 357                  } else { // invalid frame length or FrameID
 358  
 359                      if ($frame_size <= strlen($framedata)) {
 360  
 361                          if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
 362  
 363                              // next frame is valid, just skip the current frame
 364                              $framedata = substr($framedata, $frame_size);
 365                              $this->warning('Next ID3v2 frame is valid, skipping current frame.');
 366  
 367                          } else {
 368  
 369                              // next frame is invalid too, abort processing
 370                              //unset($framedata);
 371                              $framedata = null;
 372                              $this->error('Next ID3v2 frame is also invalid, aborting processing.');
 373  
 374                          }
 375  
 376                      } elseif ($frame_size == strlen($framedata)) {
 377  
 378                          // this is the last frame, just skip
 379                          $this->warning('This was the last ID3v2 frame.');
 380  
 381                      } else {
 382  
 383                          // next frame is invalid too, abort processing
 384                          //unset($framedata);
 385                          $framedata = null;
 386                          $this->warning('Invalid ID3v2 frame size, aborting.');
 387  
 388                      }
 389                      if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
 390  
 391                          switch ($frame_name) {
 392                              case "\x00\x00".'MP':
 393                              case "\x00".'MP3':
 394                              case ' MP3':
 395                              case 'MP3e':
 396                              case "\x00".'MP':
 397                              case ' MP':
 398                              case 'MP3':
 399                                  $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
 400                                  break;
 401  
 402                              default:
 403                                  $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
 404                                  break;
 405                          }
 406  
 407                      } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
 408  
 409                          $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');
 410  
 411                      } else {
 412  
 413                          $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
 414  
 415                      }
 416  
 417                  }
 418                  $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
 419  
 420              }
 421  
 422          }
 423  
 424  
 425      //    Footer
 426  
 427      //    The footer is a copy of the header, but with a different identifier.
 428      //        ID3v2 identifier           "3DI"
 429      //        ID3v2 version              $04 00
 430      //        ID3v2 flags                %abcd0000
 431      //        ID3v2 size             4 * %0xxxxxxx
 432  
 433          if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
 434              $footer = $this->fread(10);
 435              if (substr($footer, 0, 3) == '3DI') {
 436                  $thisfile_id3v2['footer'] = true;
 437                  $thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
 438                  $thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
 439              }
 440              if ($thisfile_id3v2['majorversion_footer'] <= 4) {
 441                  $id3_flags = ord($footer[5]);
 442                  $thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
 443                  $thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
 444                  $thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
 445                  $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
 446  
 447                  $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
 448              }
 449          } // end footer
 450  
 451          if (isset($thisfile_id3v2['comments']['genre'])) {
 452              $genres = array();
 453              foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
 454                  foreach ($this->ParseID3v2GenreString($value) as $genre) {
 455                      $genres[] = $genre;
 456                  }
 457              }
 458              $thisfile_id3v2['comments']['genre'] = array_unique($genres);
 459              unset($key, $value, $genres, $genre);
 460          }
 461  
 462          if (isset($thisfile_id3v2['comments']['track_number'])) {
 463              foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
 464                  if (strstr($value, '/')) {
 465                      list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
 466                  }
 467              }
 468          }
 469  
 470          if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
 471              $thisfile_id3v2['comments']['year'] = array($matches[1]);
 472          }
 473  
 474  
 475          if (!empty($thisfile_id3v2['TXXX'])) {
 476              // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
 477              foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
 478                  switch ($txxx_array['description']) {
 479                      case 'replaygain_track_gain':
 480                          if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
 481                              $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
 482                          }
 483                          break;
 484                      case 'replaygain_track_peak':
 485                          if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
 486                              $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
 487                          }
 488                          break;
 489                      case 'replaygain_album_gain':
 490                          if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
 491                              $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
 492                          }
 493                          break;
 494                  }
 495              }
 496          }
 497  
 498  
 499          // Set avdataoffset
 500          $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
 501          if (isset($thisfile_id3v2['footer'])) {
 502              $info['avdataoffset'] += 10;
 503          }
 504  
 505          return true;
 506      }
 507  
 508      /**
 509       * @param string $genrestring
 510       *
 511       * @return array
 512       */
 513  	public function ParseID3v2GenreString($genrestring) {
 514          // Parse genres into arrays of genreName and genreID
 515          // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
 516          // ID3v2.4.x: '21' $00 'Eurodisco' $00
 517          $clean_genres = array();
 518  
 519          // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
 520          if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
 521              // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
 522              // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
 523              if (preg_match('#/#', $genrestring)) {
 524                  $genrestring = str_replace('/', "\x00", $genrestring);
 525                  $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
 526                  $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
 527              }
 528  
 529              // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
 530              if (preg_match('#;#', $genrestring)) {
 531                  $genrestring = str_replace(';', "\x00", $genrestring);
 532              }
 533          }
 534  
 535  
 536          if (strpos($genrestring, "\x00") === false) {
 537              $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
 538          }
 539  
 540          $genre_elements = explode("\x00", $genrestring);
 541          foreach ($genre_elements as $element) {
 542              $element = trim($element);
 543              if ($element) {
 544                  if (preg_match('#^[0-9]{1,3}$#', $element)) {
 545                      $clean_genres[] = getid3_id3v1::LookupGenreName($element);
 546                  } else {
 547                      $clean_genres[] = str_replace('((', '(', $element);
 548                  }
 549              }
 550          }
 551          return $clean_genres;
 552      }
 553  
 554      /**
 555       * @param array $parsedFrame
 556       *
 557       * @return bool
 558       */
 559  	public function ParseID3v2Frame(&$parsedFrame) {
 560  
 561          // shortcuts
 562          $info = &$this->getid3->info;
 563          $id3v2_majorversion = $info['id3v2']['majorversion'];
 564  
 565          $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
 566          if (empty($parsedFrame['framenamelong'])) {
 567              unset($parsedFrame['framenamelong']);
 568          }
 569          $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
 570          if (empty($parsedFrame['framenameshort'])) {
 571              unset($parsedFrame['framenameshort']);
 572          }
 573  
 574          if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
 575              if ($id3v2_majorversion == 3) {
 576                  //    Frame Header Flags
 577                  //    %abc00000 %ijk00000
 578                  $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
 579                  $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
 580                  $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
 581                  $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
 582                  $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
 583                  $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
 584  
 585              } elseif ($id3v2_majorversion == 4) {
 586                  //    Frame Header Flags
 587                  //    %0abc0000 %0h00kmnp
 588                  $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
 589                  $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
 590                  $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
 591                  $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
 592                  $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
 593                  $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
 594                  $parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
 595                  $parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
 596  
 597                  // Frame-level de-unsynchronisation - ID3v2.4
 598                  if ($parsedFrame['flags']['Unsynchronisation']) {
 599                      $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
 600                  }
 601  
 602                  if ($parsedFrame['flags']['DataLengthIndicator']) {
 603                      $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
 604                      $parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
 605                  }
 606              }
 607  
 608              //    Frame-level de-compression
 609              if ($parsedFrame['flags']['compression']) {
 610                  $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
 611                  if (!function_exists('gzuncompress')) {
 612                      $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
 613                  } else {
 614                      if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
 615                      //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
 616                          $parsedFrame['data'] = $decompresseddata;
 617                          unset($decompresseddata);
 618                      } else {
 619                          $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
 620                      }
 621                  }
 622              }
 623          }
 624  
 625          if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
 626              if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
 627                  $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
 628              }
 629          }
 630  
 631          if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
 632  
 633              $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
 634              switch ($parsedFrame['frame_name']) {
 635                  case 'WCOM':
 636                      $warning .= ' (this is known to happen with files tagged by RioPort)';
 637                      break;
 638  
 639                  default:
 640                      break;
 641              }
 642              $this->warning($warning);
 643  
 644          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
 645              (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
 646              //   There may be more than one 'UFID' frame in a tag,
 647              //   but only one with the same 'Owner identifier'.
 648              // <Header for 'Unique file identifier', ID: 'UFID'>
 649              // Owner identifier        <text string> $00
 650              // Identifier              <up to 64 bytes binary data>
 651              $exploded = explode("\x00", $parsedFrame['data'], 2);
 652              $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
 653              $parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
 654  
 655          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
 656                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
 657              //   There may be more than one 'TXXX' frame in each tag,
 658              //   but only one with the same description.
 659              // <Header for 'User defined text information frame', ID: 'TXXX'>
 660              // Text encoding     $xx
 661              // Description       <text string according to encoding> $00 (00)
 662              // Value             <text string according to encoding>
 663  
 664              $frame_offset = 0;
 665              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 666              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 667              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
 668                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 669                  $frame_textencoding_terminator = "\x00";
 670              }
 671              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
 672              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
 673                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 674              }
 675              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
 676              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
 677              $parsedFrame['encodingid']  = $frame_textencoding;
 678              $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
 679  
 680              $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
 681              $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
 682              $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
 683              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
 684                  $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
 685                  if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
 686                      $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
 687                  } else {
 688                      $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
 689                  }
 690              }
 691              //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
 692  
 693  
 694          } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
 695              //   There may only be one text information frame of its kind in an tag.
 696              // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
 697              // excluding 'TXXX' described in 4.2.6.>
 698              // Text encoding                $xx
 699              // Information                  <text string(s) according to encoding>
 700  
 701              $frame_offset = 0;
 702              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 703              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
 704                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 705              }
 706  
 707              $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
 708              $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
 709  
 710              $parsedFrame['encodingid'] = $frame_textencoding;
 711              $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
 712              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
 713                  // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
 714                  // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
 715                  // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
 716                  // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
 717                  switch ($parsedFrame['encoding']) {
 718                      case 'UTF-16':
 719                      case 'UTF-16BE':
 720                      case 'UTF-16LE':
 721                          $wordsize = 2;
 722                          break;
 723                      case 'ISO-8859-1':
 724                      case 'UTF-8':
 725                      default:
 726                          $wordsize = 1;
 727                          break;
 728                  }
 729                  $Txxx_elements = array();
 730                  $Txxx_elements_start_offset = 0;
 731                  for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
 732                      if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
 733                          $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
 734                          $Txxx_elements_start_offset = $i + $wordsize;
 735                      }
 736                  }
 737                  $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
 738                  foreach ($Txxx_elements as $Txxx_element) {
 739                      $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
 740                      if (!empty($string)) {
 741                          $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
 742                      }
 743                  }
 744                  unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
 745              }
 746  
 747          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
 748                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
 749              //   There may be more than one 'WXXX' frame in each tag,
 750              //   but only one with the same description
 751              // <Header for 'User defined URL link frame', ID: 'WXXX'>
 752              // Text encoding     $xx
 753              // Description       <text string according to encoding> $00 (00)
 754              // URL               <text string>
 755  
 756              $frame_offset = 0;
 757              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 758              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 759              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
 760                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 761                  $frame_textencoding_terminator = "\x00";
 762              }
 763              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
 764              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
 765                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 766              }
 767              $parsedFrame['encodingid']  = $frame_textencoding;
 768              $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
 769              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
 770              $parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
 771              $parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
 772              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
 773  
 774              if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
 775                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
 776              }
 777              unset($parsedFrame['data']);
 778  
 779  
 780          } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
 781              //   There may only be one URL link frame of its kind in a tag,
 782              //   except when stated otherwise in the frame description
 783              // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
 784              // described in 4.3.2.>
 785              // URL              <text string>
 786  
 787              $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
 788              if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
 789                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
 790              }
 791              unset($parsedFrame['data']);
 792  
 793  
 794          } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
 795                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
 796              // http://id3.org/id3v2.3.0#sec4.4
 797              //   There may only be one 'IPL' frame in each tag
 798              // <Header for 'User defined URL link frame', ID: 'IPL'>
 799              // Text encoding     $xx
 800              // People list strings    <textstrings>
 801  
 802              $frame_offset = 0;
 803              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 804              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
 805                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 806              }
 807              $parsedFrame['encodingid'] = $frame_textencoding;
 808              $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
 809              $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
 810  
 811              // https://www.getid3.org/phpBB3/viewtopic.php?t=1369
 812              // "this tag typically contains null terminated strings, which are associated in pairs"
 813              // "there are users that use the tag incorrectly"
 814              $IPLS_parts = array();
 815              if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
 816                  $IPLS_parts_unsorted = array();
 817                  if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
 818                      // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
 819                      $thisILPS  = '';
 820                      for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
 821                          $twobytes = substr($parsedFrame['data_raw'], $i, 2);
 822                          if ($twobytes === "\x00\x00") {
 823                              $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
 824                              $thisILPS  = '';
 825                          } else {
 826                              $thisILPS .= $twobytes;
 827                          }
 828                      }
 829                      if (strlen($thisILPS) > 2) { // 2-byte BOM
 830                          $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
 831                      }
 832                  } else {
 833                      // ISO-8859-1 or UTF-8 or other single-byte-null character set
 834                      $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
 835                  }
 836                  if (count($IPLS_parts_unsorted) == 1) {
 837                      // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
 838                      foreach ($IPLS_parts_unsorted as $key => $value) {
 839                          $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
 840                          $position = '';
 841                          foreach ($IPLS_parts_sorted as $person) {
 842                              $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
 843                          }
 844                      }
 845                  } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
 846                      $position = '';
 847                      $person   = '';
 848                      foreach ($IPLS_parts_unsorted as $key => $value) {
 849                          if (($key % 2) == 0) {
 850                              $position = $value;
 851                          } else {
 852                              $person   = $value;
 853                              $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
 854                              $position = '';
 855                              $person   = '';
 856                          }
 857                      }
 858                  } else {
 859                      foreach ($IPLS_parts_unsorted as $key => $value) {
 860                          $IPLS_parts[] = array($value);
 861                      }
 862                  }
 863  
 864              } else {
 865                  $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
 866              }
 867              $parsedFrame['data'] = $IPLS_parts;
 868  
 869              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
 870                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
 871              }
 872  
 873  
 874          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
 875                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
 876              //   There may only be one 'MCDI' frame in each tag
 877              // <Header for 'Music CD identifier', ID: 'MCDI'>
 878              // CD TOC                <binary data>
 879  
 880              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
 881                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
 882              }
 883  
 884  
 885          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
 886                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
 887              //   There may only be one 'ETCO' frame in each tag
 888              // <Header for 'Event timing codes', ID: 'ETCO'>
 889              // Time stamp format    $xx
 890              //   Where time stamp format is:
 891              // $01  (32-bit value) MPEG frames from beginning of file
 892              // $02  (32-bit value) milliseconds from beginning of file
 893              //   Followed by a list of key events in the following format:
 894              // Type of event   $xx
 895              // Time stamp      $xx (xx ...)
 896              //   The 'Time stamp' is set to zero if directly at the beginning of the sound
 897              //   or after the previous event. All events MUST be sorted in chronological order.
 898  
 899              $frame_offset = 0;
 900              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 901  
 902              while ($frame_offset < strlen($parsedFrame['data'])) {
 903                  $parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
 904                  $parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
 905                  $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
 906                  $frame_offset += 4;
 907              }
 908              unset($parsedFrame['data']);
 909  
 910  
 911          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
 912                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
 913              //   There may only be one 'MLLT' frame in each tag
 914              // <Header for 'Location lookup table', ID: 'MLLT'>
 915              // MPEG frames between reference  $xx xx
 916              // Bytes between reference        $xx xx xx
 917              // Milliseconds between reference $xx xx xx
 918              // Bits for bytes deviation       $xx
 919              // Bits for milliseconds dev.     $xx
 920              //   Then for every reference the following data is included;
 921              // Deviation in bytes         %xxx....
 922              // Deviation in milliseconds  %xxx....
 923  
 924              $frame_offset = 0;
 925              $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
 926              $parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
 927              $parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
 928              $parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
 929              $parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
 930              $parsedFrame['data'] = substr($parsedFrame['data'], 10);
 931              $deviationbitstream = '';
 932              while ($frame_offset < strlen($parsedFrame['data'])) {
 933                  $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
 934              }
 935              $reference_counter = 0;
 936              while (strlen($deviationbitstream) > 0) {
 937                  $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
 938                  $parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
 939                  $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
 940                  $reference_counter++;
 941              }
 942              unset($parsedFrame['data']);
 943  
 944  
 945          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
 946                    (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
 947              //   There may only be one 'SYTC' frame in each tag
 948              // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
 949              // Time stamp format   $xx
 950              // Tempo data          <binary data>
 951              //   Where time stamp format is:
 952              // $01  (32-bit value) MPEG frames from beginning of file
 953              // $02  (32-bit value) milliseconds from beginning of file
 954  
 955              $frame_offset = 0;
 956              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 957              $timestamp_counter = 0;
 958              while ($frame_offset < strlen($parsedFrame['data'])) {
 959                  $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 960                  if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
 961                      $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
 962                  }
 963                  $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
 964                  $frame_offset += 4;
 965                  $timestamp_counter++;
 966              }
 967              unset($parsedFrame['data']);
 968  
 969  
 970          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
 971                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
 972              //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
 973              //   in each tag, but only one with the same language and content descriptor.
 974              // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
 975              // Text encoding        $xx
 976              // Language             $xx xx xx
 977              // Content descriptor   <text string according to encoding> $00 (00)
 978              // Lyrics/text          <full text string according to encoding>
 979  
 980              $frame_offset = 0;
 981              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 982              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 983              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
 984                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 985                  $frame_textencoding_terminator = "\x00";
 986              }
 987              $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
 988              $frame_offset += 3;
 989              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
 990              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
 991                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 992              }
 993              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
 994              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
 995              $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
 996              $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
 997  
 998              $parsedFrame['encodingid']   = $frame_textencoding;
 999              $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1000  
1001              $parsedFrame['language']     = $frame_language;
1002              $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1003              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1004                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1005              }
1006              unset($parsedFrame['data']);
1007  
1008  
1009          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
1010                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
1011              //   There may be more than one 'SYLT' frame in each tag,
1012              //   but only one with the same language and content descriptor.
1013              // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1014              // Text encoding        $xx
1015              // Language             $xx xx xx
1016              // Time stamp format    $xx
1017              //   $01  (32-bit value) MPEG frames from beginning of file
1018              //   $02  (32-bit value) milliseconds from beginning of file
1019              // Content type         $xx
1020              // Content descriptor   <text string according to encoding> $00 (00)
1021              //   Terminated text to be synced (typically a syllable)
1022              //   Sync identifier (terminator to above string)   $00 (00)
1023              //   Time stamp                                     $xx (xx ...)
1024  
1025              $frame_offset = 0;
1026              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1027              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1028              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1029                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1030                  $frame_textencoding_terminator = "\x00";
1031              }
1032              $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1033              $frame_offset += 3;
1034              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1035              $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1036              $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1037              $parsedFrame['encodingid']      = $frame_textencoding;
1038              $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
1039  
1040              $parsedFrame['language']        = $frame_language;
1041              $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1042  
1043              $timestampindex = 0;
1044              $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1045              while (strlen($frame_remainingdata)) {
1046                  $frame_offset = 0;
1047                  $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1048                  if ($frame_terminatorpos === false) {
1049                      $frame_remainingdata = '';
1050                  } else {
1051                      if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1052                          $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1053                      }
1054                      $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1055  
1056                      $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
1057                      if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
1058                          // timestamp probably omitted for first data item
1059                      } else {
1060                          $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1061                          $frame_remainingdata = substr($frame_remainingdata, 4);
1062                      }
1063                      $timestampindex++;
1064                  }
1065              }
1066              unset($parsedFrame['data']);
1067  
1068  
1069          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
1070                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
1071              //   There may be more than one comment frame in each tag,
1072              //   but only one with the same language and content descriptor.
1073              // <Header for 'Comment', ID: 'COMM'>
1074              // Text encoding          $xx
1075              // Language               $xx xx xx
1076              // Short content descrip. <text string according to encoding> $00 (00)
1077              // The actual text        <full text string according to encoding>
1078  
1079              if (strlen($parsedFrame['data']) < 5) {
1080  
1081                  $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1082  
1083              } else {
1084  
1085                  $frame_offset = 0;
1086                  $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1087                  $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1088                  if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1089                      $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1090                      $frame_textencoding_terminator = "\x00";
1091                  }
1092                  $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1093                  $frame_offset += 3;
1094                  $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1095                  if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1096                      $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1097                  }
1098                  $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1099                  $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1100                  $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1101                  $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
1102  
1103                  $parsedFrame['encodingid']   = $frame_textencoding;
1104                  $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1105  
1106                  $parsedFrame['language']     = $frame_language;
1107                  $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1108                  $parsedFrame['data']         = $frame_text;
1109                  if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1110                      $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1111                      if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1112                          $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1113                      } else {
1114                          $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1115                      }
1116                  }
1117  
1118              }
1119  
1120          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1121              //   There may be more than one 'RVA2' frame in each tag,
1122              //   but only one with the same identification string
1123              // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1124              // Identification          <text string> $00
1125              //   The 'identification' string is used to identify the situation and/or
1126              //   device where this adjustment should apply. The following is then
1127              //   repeated for every channel:
1128              // Type of channel         $xx
1129              // Volume adjustment       $xx xx
1130              // Bits representing peak  $xx
1131              // Peak volume             $xx (xx ...)
1132  
1133              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1134              $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1135              if (ord($frame_idstring) === 0) {
1136                  $frame_idstring = '';
1137              }
1138              $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1139              $parsedFrame['description'] = $frame_idstring;
1140              $RVA2channelcounter = 0;
1141              while (strlen($frame_remainingdata) >= 5) {
1142                  $frame_offset = 0;
1143                  $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1144                  $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
1145                  $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1146                  $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1147                  $frame_offset += 2;
1148                  $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1149                  if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1150                      $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1151                      break;
1152                  }
1153                  $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1154                  $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1155                  $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1156                  $RVA2channelcounter++;
1157              }
1158              unset($parsedFrame['data']);
1159  
1160  
1161          } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1162                    (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1163              //   There may only be one 'RVA' frame in each tag
1164              // <Header for 'Relative volume adjustment', ID: 'RVA'>
1165              // ID3v2.2 => Increment/decrement     %000000ba
1166              // ID3v2.3 => Increment/decrement     %00fedcba
1167              // Bits used for volume descr.        $xx
1168              // Relative volume change, right      $xx xx (xx ...) // a
1169              // Relative volume change, left       $xx xx (xx ...) // b
1170              // Peak volume right                  $xx xx (xx ...)
1171              // Peak volume left                   $xx xx (xx ...)
1172              //   ID3v2.3 only, optional (not present in ID3v2.2):
1173              // Relative volume change, right back $xx xx (xx ...) // c
1174              // Relative volume change, left back  $xx xx (xx ...) // d
1175              // Peak volume right back             $xx xx (xx ...)
1176              // Peak volume left back              $xx xx (xx ...)
1177              //   ID3v2.3 only, optional (not present in ID3v2.2):
1178              // Relative volume change, center     $xx xx (xx ...) // e
1179              // Peak volume center                 $xx xx (xx ...)
1180              //   ID3v2.3 only, optional (not present in ID3v2.2):
1181              // Relative volume change, bass       $xx xx (xx ...) // f
1182              // Peak volume bass                   $xx xx (xx ...)
1183  
1184              $frame_offset = 0;
1185              $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1186              $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1187              $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
1188              $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1189              $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1190              $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1191              if ($parsedFrame['incdec']['right'] === false) {
1192                  $parsedFrame['volumechange']['right'] *= -1;
1193              }
1194              $frame_offset += $frame_bytesvolume;
1195              $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1196              if ($parsedFrame['incdec']['left'] === false) {
1197                  $parsedFrame['volumechange']['left'] *= -1;
1198              }
1199              $frame_offset += $frame_bytesvolume;
1200              $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1201              $frame_offset += $frame_bytesvolume;
1202              $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1203              $frame_offset += $frame_bytesvolume;
1204              if ($id3v2_majorversion == 3) {
1205                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1206                  if (strlen($parsedFrame['data']) > 0) {
1207                      $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1208                      $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
1209                      $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1210                      if ($parsedFrame['incdec']['rightrear'] === false) {
1211                          $parsedFrame['volumechange']['rightrear'] *= -1;
1212                      }
1213                      $frame_offset += $frame_bytesvolume;
1214                      $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1215                      if ($parsedFrame['incdec']['leftrear'] === false) {
1216                          $parsedFrame['volumechange']['leftrear'] *= -1;
1217                      }
1218                      $frame_offset += $frame_bytesvolume;
1219                      $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220                      $frame_offset += $frame_bytesvolume;
1221                      $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1222                      $frame_offset += $frame_bytesvolume;
1223                  }
1224                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1225                  if (strlen($parsedFrame['data']) > 0) {
1226                      $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1227                      $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1228                      if ($parsedFrame['incdec']['center'] === false) {
1229                          $parsedFrame['volumechange']['center'] *= -1;
1230                      }
1231                      $frame_offset += $frame_bytesvolume;
1232                      $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1233                      $frame_offset += $frame_bytesvolume;
1234                  }
1235                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1236                  if (strlen($parsedFrame['data']) > 0) {
1237                      $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1238                      $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1239                      if ($parsedFrame['incdec']['bass'] === false) {
1240                          $parsedFrame['volumechange']['bass'] *= -1;
1241                      }
1242                      $frame_offset += $frame_bytesvolume;
1243                      $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1244                      $frame_offset += $frame_bytesvolume;
1245                  }
1246              }
1247              unset($parsedFrame['data']);
1248  
1249  
1250          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1251              //   There may be more than one 'EQU2' frame in each tag,
1252              //   but only one with the same identification string
1253              // <Header of 'Equalisation (2)', ID: 'EQU2'>
1254              // Interpolation method  $xx
1255              //   $00  Band
1256              //   $01  Linear
1257              // Identification        <text string> $00
1258              //   The following is then repeated for every adjustment point
1259              // Frequency          $xx xx
1260              // Volume adjustment  $xx xx
1261  
1262              $frame_offset = 0;
1263              $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1264              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1265              $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1266              if (ord($frame_idstring) === 0) {
1267                  $frame_idstring = '';
1268              }
1269              $parsedFrame['description'] = $frame_idstring;
1270              $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1271              while (strlen($frame_remainingdata)) {
1272                  $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1273                  $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1274                  $frame_remainingdata = substr($frame_remainingdata, 4);
1275              }
1276              $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1277              unset($parsedFrame['data']);
1278  
1279  
1280          } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
1281                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1282              //   There may only be one 'EQUA' frame in each tag
1283              // <Header for 'Relative volume adjustment', ID: 'EQU'>
1284              // Adjustment bits    $xx
1285              //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1286              //   nearest byte) for every equalisation band in the following format,
1287              //   giving a frequency range of 0 - 32767Hz:
1288              // Increment/decrement   %x (MSB of the Frequency)
1289              // Frequency             (lower 15 bits)
1290              // Adjustment            $xx (xx ...)
1291  
1292              $frame_offset = 0;
1293              $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1294              $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1295  
1296              $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1297              while (strlen($frame_remainingdata) > 0) {
1298                  $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1299                  $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
1300                  $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1301                  $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1302                  $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1303                  if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1304                      $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1305                  }
1306                  $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1307              }
1308              unset($parsedFrame['data']);
1309  
1310  
1311          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
1312                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1313              //   There may only be one 'RVRB' frame in each tag.
1314              // <Header for 'Reverb', ID: 'RVRB'>
1315              // Reverb left (ms)                 $xx xx
1316              // Reverb right (ms)                $xx xx
1317              // Reverb bounces, left             $xx
1318              // Reverb bounces, right            $xx
1319              // Reverb feedback, left to left    $xx
1320              // Reverb feedback, left to right   $xx
1321              // Reverb feedback, right to right  $xx
1322              // Reverb feedback, right to left   $xx
1323              // Premix left to right             $xx
1324              // Premix right to left             $xx
1325  
1326              $frame_offset = 0;
1327              $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1328              $frame_offset += 2;
1329              $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1330              $frame_offset += 2;
1331              $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1332              $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1333              $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1334              $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1335              $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1336              $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1337              $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1338              $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1339              unset($parsedFrame['data']);
1340  
1341  
1342          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
1343                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1344              //   There may be several pictures attached to one file,
1345              //   each in their individual 'APIC' frame, but only one
1346              //   with the same content descriptor
1347              // <Header for 'Attached picture', ID: 'APIC'>
1348              // Text encoding      $xx
1349              // ID3v2.3+ => MIME type          <text string> $00
1350              // ID3v2.2  => Image format       $xx xx xx
1351              // Picture type       $xx
1352              // Description        <text string according to encoding> $00 (00)
1353              // Picture data       <binary data>
1354  
1355              $frame_offset = 0;
1356              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1357              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1358              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1359                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1360                  $frame_textencoding_terminator = "\x00";
1361              }
1362  
1363              if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1364                  $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1365                  if (strtolower($frame_imagetype) == 'ima') {
1366                      // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1367                      // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1368                      $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1369                      $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1370                      if (ord($frame_mimetype) === 0) {
1371                          $frame_mimetype = '';
1372                      }
1373                      $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1374                      if ($frame_imagetype == 'JPEG') {
1375                          $frame_imagetype = 'JPG';
1376                      }
1377                      $frame_offset = $frame_terminatorpos + strlen("\x00");
1378                  } else {
1379                      $frame_offset += 3;
1380                  }
1381              }
1382              if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1383                  $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1384                  $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1385                  if (ord($frame_mimetype) === 0) {
1386                      $frame_mimetype = '';
1387                  }
1388                  $frame_offset = $frame_terminatorpos + strlen("\x00");
1389              }
1390  
1391              $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1392  
1393              if ($frame_offset >= $parsedFrame['datalength']) {
1394                  $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
1395              } else {
1396                  $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1397                  if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1398                      $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1399                  }
1400                  $parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1401                  $parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1402                  $parsedFrame['encodingid']    = $frame_textencoding;
1403                  $parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);
1404  
1405                  if ($id3v2_majorversion == 2) {
1406                      $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
1407                  } else {
1408                      $parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
1409                  }
1410                  $parsedFrame['picturetypeid'] = $frame_picturetype;
1411                  $parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
1412                  $parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1413                  $parsedFrame['datalength']    = strlen($parsedFrame['data']);
1414  
1415                  $parsedFrame['image_mime']    = '';
1416                  $imageinfo = array();
1417                  if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1418                      if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1419                          $parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
1420                          if ($imagechunkcheck[0]) {
1421                              $parsedFrame['image_width']  = $imagechunkcheck[0];
1422                          }
1423                          if ($imagechunkcheck[1]) {
1424                              $parsedFrame['image_height'] = $imagechunkcheck[1];
1425                          }
1426                      }
1427                  }
1428  
1429                  do {
1430                      if ($this->getid3->option_save_attachments === false) {
1431                          // skip entirely
1432                          unset($parsedFrame['data']);
1433                          break;
1434                      }
1435                      $dir = '';
1436                      if ($this->getid3->option_save_attachments === true) {
1437                          // great
1438  /*
1439                      } elseif (is_int($this->getid3->option_save_attachments)) {
1440                          if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1441                              // too big, skip
1442                              $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1443                              unset($parsedFrame['data']);
1444                              break;
1445                          }
1446  */
1447                      } elseif (is_string($this->getid3->option_save_attachments)) {
1448                          $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1449                          if (!is_dir($dir) || !getID3::is_writable($dir)) {
1450                              // cannot write, skip
1451                              $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1452                              unset($parsedFrame['data']);
1453                              break;
1454                          }
1455                      }
1456                      // if we get this far, must be OK
1457                      if (is_string($this->getid3->option_save_attachments)) {
1458                          $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1459                          if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
1460                              file_put_contents($destination_filename, $parsedFrame['data']);
1461                          } else {
1462                              $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1463                          }
1464                          $parsedFrame['data_filename'] = $destination_filename;
1465                          unset($parsedFrame['data']);
1466                      } else {
1467                          if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1468                              if (!isset($info['id3v2']['comments']['picture'])) {
1469                                  $info['id3v2']['comments']['picture'] = array();
1470                              }
1471                              $comments_picture_data = array();
1472                              foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1473                                  if (isset($parsedFrame[$picture_key])) {
1474                                      $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1475                                  }
1476                              }
1477                              $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1478                              unset($comments_picture_data);
1479                          }
1480                      }
1481                  } while (false);
1482              }
1483  
1484          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
1485                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1486              //   There may be more than one 'GEOB' frame in each tag,
1487              //   but only one with the same content descriptor
1488              // <Header for 'General encapsulated object', ID: 'GEOB'>
1489              // Text encoding          $xx
1490              // MIME type              <text string> $00
1491              // Filename               <text string according to encoding> $00 (00)
1492              // Content description    <text string according to encoding> $00 (00)
1493              // Encapsulated object    <binary data>
1494  
1495              $frame_offset = 0;
1496              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1497              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1498              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1499                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1500                  $frame_textencoding_terminator = "\x00";
1501              }
1502              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1503              $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1504              if (ord($frame_mimetype) === 0) {
1505                  $frame_mimetype = '';
1506              }
1507              $frame_offset = $frame_terminatorpos + strlen("\x00");
1508  
1509              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1510              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1511                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1512              }
1513              $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1514              if (ord($frame_filename) === 0) {
1515                  $frame_filename = '';
1516              }
1517              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1518  
1519              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1520              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1521                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1522              }
1523              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1524              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1525              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1526  
1527              $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1528              $parsedFrame['encodingid']  = $frame_textencoding;
1529              $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1530  
1531              $parsedFrame['mime']        = $frame_mimetype;
1532              $parsedFrame['filename']    = $frame_filename;
1533              unset($parsedFrame['data']);
1534  
1535  
1536          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
1537                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
1538              //   There may only be one 'PCNT' frame in each tag.
1539              //   When the counter reaches all one's, one byte is inserted in
1540              //   front of the counter thus making the counter eight bits bigger
1541              // <Header for 'Play counter', ID: 'PCNT'>
1542              // Counter        $xx xx xx xx (xx ...)
1543  
1544              $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1545  
1546  
1547          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
1548                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
1549              //   There may be more than one 'POPM' frame in each tag,
1550              //   but only one with the same email address
1551              // <Header for 'Popularimeter', ID: 'POPM'>
1552              // Email to user   <text string> $00
1553              // Rating          $xx
1554              // Counter         $xx xx xx xx (xx ...)
1555  
1556              $frame_offset = 0;
1557              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1558              $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1559              if (ord($frame_emailaddress) === 0) {
1560                  $frame_emailaddress = '';
1561              }
1562              $frame_offset = $frame_terminatorpos + strlen("\x00");
1563              $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1564              $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1565              $parsedFrame['email']   = $frame_emailaddress;
1566              $parsedFrame['rating']  = $frame_rating;
1567              unset($parsedFrame['data']);
1568  
1569  
1570          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
1571                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
1572              //   There may only be one 'RBUF' frame in each tag
1573              // <Header for 'Recommended buffer size', ID: 'RBUF'>
1574              // Buffer size               $xx xx xx
1575              // Embedded info flag        %0000000x
1576              // Offset to next tag        $xx xx xx xx
1577  
1578              $frame_offset = 0;
1579              $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1580              $frame_offset += 3;
1581  
1582              $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1583              $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1584              $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1585              unset($parsedFrame['data']);
1586  
1587  
1588          } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1589              //   There may be more than one 'CRM' frame in a tag,
1590              //   but only one with the same 'owner identifier'
1591              // <Header for 'Encrypted meta frame', ID: 'CRM'>
1592              // Owner identifier      <textstring> $00 (00)
1593              // Content/explanation   <textstring> $00 (00)
1594              // Encrypted datablock   <binary data>
1595  
1596              $frame_offset = 0;
1597              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1598              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1599              $frame_offset = $frame_terminatorpos + strlen("\x00");
1600  
1601              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1602              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1603              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1604              $frame_offset = $frame_terminatorpos + strlen("\x00");
1605  
1606              $parsedFrame['ownerid']     = $frame_ownerid;
1607              $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1608              unset($parsedFrame['data']);
1609  
1610  
1611          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
1612                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
1613              //   There may be more than one 'AENC' frames in a tag,
1614              //   but only one with the same 'Owner identifier'
1615              // <Header for 'Audio encryption', ID: 'AENC'>
1616              // Owner identifier   <text string> $00
1617              // Preview start      $xx xx
1618              // Preview length     $xx xx
1619              // Encryption info    <binary data>
1620  
1621              $frame_offset = 0;
1622              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1623              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1624              if (ord($frame_ownerid) === 0) {
1625                  $frame_ownerid = '';
1626              }
1627              $frame_offset = $frame_terminatorpos + strlen("\x00");
1628              $parsedFrame['ownerid'] = $frame_ownerid;
1629              $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1630              $frame_offset += 2;
1631              $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1632              $frame_offset += 2;
1633              $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1634              unset($parsedFrame['data']);
1635  
1636  
1637          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
1638                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
1639              //   There may be more than one 'LINK' frame in a tag,
1640              //   but only one with the same contents
1641              // <Header for 'Linked information', ID: 'LINK'>
1642              // ID3v2.3+ => Frame identifier   $xx xx xx xx
1643              // ID3v2.2  => Frame identifier   $xx xx xx
1644              // URL                            <text string> $00
1645              // ID and additional data         <text string(s)>
1646  
1647              $frame_offset = 0;
1648              if ($id3v2_majorversion == 2) {
1649                  $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1650                  $frame_offset += 3;
1651              } else {
1652                  $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1653                  $frame_offset += 4;
1654              }
1655  
1656              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1657              $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1658              if (ord($frame_url) === 0) {
1659                  $frame_url = '';
1660              }
1661              $frame_offset = $frame_terminatorpos + strlen("\x00");
1662              $parsedFrame['url'] = $frame_url;
1663  
1664              $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1665              if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1666                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1667              }
1668              unset($parsedFrame['data']);
1669  
1670  
1671          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1672              //   There may only be one 'POSS' frame in each tag
1673              // <Head for 'Position synchronisation', ID: 'POSS'>
1674              // Time stamp format         $xx
1675              // Position                  $xx (xx ...)
1676  
1677              $frame_offset = 0;
1678              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1679              $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1680              unset($parsedFrame['data']);
1681  
1682  
1683          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1684              //   There may be more than one 'Terms of use' frame in a tag,
1685              //   but only one with the same 'Language'
1686              // <Header for 'Terms of use frame', ID: 'USER'>
1687              // Text encoding        $xx
1688              // Language             $xx xx xx
1689              // The actual text      <text string according to encoding>
1690  
1691              $frame_offset = 0;
1692              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1693              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1694                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1695              }
1696              $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1697              $frame_offset += 3;
1698              $parsedFrame['language']     = $frame_language;
1699              $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1700              $parsedFrame['encodingid']   = $frame_textencoding;
1701              $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1702  
1703              $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1704              $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1705              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1706                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1707              }
1708              unset($parsedFrame['data']);
1709  
1710  
1711          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1712              //   There may only be one 'OWNE' frame in a tag
1713              // <Header for 'Ownership frame', ID: 'OWNE'>
1714              // Text encoding     $xx
1715              // Price paid        <text string> $00
1716              // Date of purch.    <text string>
1717              // Seller            <text string according to encoding>
1718  
1719              $frame_offset = 0;
1720              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1721              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1722                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1723              }
1724              $parsedFrame['encodingid'] = $frame_textencoding;
1725              $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1726  
1727              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1728              $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1729              $frame_offset = $frame_terminatorpos + strlen("\x00");
1730  
1731              $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1732              $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1733              $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1734  
1735              $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1736              if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1737                  $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1738              }
1739              $frame_offset += 8;
1740  
1741              $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1742              $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1743              unset($parsedFrame['data']);
1744  
1745  
1746          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1747              //   There may be more than one 'commercial frame' in a tag,
1748              //   but no two may be identical
1749              // <Header for 'Commercial frame', ID: 'COMR'>
1750              // Text encoding      $xx
1751              // Price string       <text string> $00
1752              // Valid until        <text string>
1753              // Contact URL        <text string> $00
1754              // Received as        $xx
1755              // Name of seller     <text string according to encoding> $00 (00)
1756              // Description        <text string according to encoding> $00 (00)
1757              // Picture MIME type  <string> $00
1758              // Seller logo        <binary data>
1759  
1760              $frame_offset = 0;
1761              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1762              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1763              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1764                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1765                  $frame_textencoding_terminator = "\x00";
1766              }
1767  
1768              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1769              $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1770              $frame_offset = $frame_terminatorpos + strlen("\x00");
1771              $frame_rawpricearray = explode('/', $frame_pricestring);
1772              foreach ($frame_rawpricearray as $key => $val) {
1773                  $frame_currencyid = substr($val, 0, 3);
1774                  $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1775                  $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
1776              }
1777  
1778              $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1779              $frame_offset += 8;
1780  
1781              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1782              $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1783              $frame_offset = $frame_terminatorpos + strlen("\x00");
1784  
1785              $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1786  
1787              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1788              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1789                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1790              }
1791              $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1792              if (ord($frame_sellername) === 0) {
1793                  $frame_sellername = '';
1794              }
1795              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1796  
1797              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1798              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1799                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1800              }
1801              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1802              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1803              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1804  
1805              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1806              $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1807              $frame_offset = $frame_terminatorpos + strlen("\x00");
1808  
1809              $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1810  
1811              $parsedFrame['encodingid']        = $frame_textencoding;
1812              $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1813  
1814              $parsedFrame['pricevaliduntil']   = $frame_datestring;
1815              $parsedFrame['contacturl']        = $frame_contacturl;
1816              $parsedFrame['receivedasid']      = $frame_receivedasid;
1817              $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
1818              $parsedFrame['sellername']        = $frame_sellername;
1819              $parsedFrame['mime']              = $frame_mimetype;
1820              $parsedFrame['logo']              = $frame_sellerlogo;
1821              unset($parsedFrame['data']);
1822  
1823  
1824          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1825              //   There may be several 'ENCR' frames in a tag,
1826              //   but only one containing the same symbol
1827              //   and only one containing the same owner identifier
1828              // <Header for 'Encryption method registration', ID: 'ENCR'>
1829              // Owner identifier    <text string> $00
1830              // Method symbol       $xx
1831              // Encryption data     <binary data>
1832  
1833              $frame_offset = 0;
1834              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1835              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1836              if (ord($frame_ownerid) === 0) {
1837                  $frame_ownerid = '';
1838              }
1839              $frame_offset = $frame_terminatorpos + strlen("\x00");
1840  
1841              $parsedFrame['ownerid']      = $frame_ownerid;
1842              $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1843              $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1844  
1845  
1846          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1847  
1848              //   There may be several 'GRID' frames in a tag,
1849              //   but only one containing the same symbol
1850              //   and only one containing the same owner identifier
1851              // <Header for 'Group ID registration', ID: 'GRID'>
1852              // Owner identifier      <text string> $00
1853              // Group symbol          $xx
1854              // Group dependent data  <binary data>
1855  
1856              $frame_offset = 0;
1857              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1858              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1859              if (ord($frame_ownerid) === 0) {
1860                  $frame_ownerid = '';
1861              }
1862              $frame_offset = $frame_terminatorpos + strlen("\x00");
1863  
1864              $parsedFrame['ownerid']       = $frame_ownerid;
1865              $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1866              $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1867  
1868  
1869          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1870              //   The tag may contain more than one 'PRIV' frame
1871              //   but only with different contents
1872              // <Header for 'Private frame', ID: 'PRIV'>
1873              // Owner identifier      <text string> $00
1874              // The private data      <binary data>
1875  
1876              $frame_offset = 0;
1877              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1878              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1879              if (ord($frame_ownerid) === 0) {
1880                  $frame_ownerid = '';
1881              }
1882              $frame_offset = $frame_terminatorpos + strlen("\x00");
1883  
1884              $parsedFrame['ownerid'] = $frame_ownerid;
1885              $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1886  
1887  
1888          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1889              //   There may be more than one 'signature frame' in a tag,
1890              //   but no two may be identical
1891              // <Header for 'Signature frame', ID: 'SIGN'>
1892              // Group symbol      $xx
1893              // Signature         <binary data>
1894  
1895              $frame_offset = 0;
1896              $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1897              $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1898  
1899  
1900          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1901              //   There may only be one 'seek frame' in a tag
1902              // <Header for 'Seek frame', ID: 'SEEK'>
1903              // Minimum offset to next tag       $xx xx xx xx
1904  
1905              $frame_offset = 0;
1906              $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1907  
1908  
1909          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1910              //   There may only be one 'audio seek point index' frame in a tag
1911              // <Header for 'Seek Point Index', ID: 'ASPI'>
1912              // Indexed data start (S)         $xx xx xx xx
1913              // Indexed data length (L)        $xx xx xx xx
1914              // Number of index points (N)     $xx xx
1915              // Bits per index point (b)       $xx
1916              //   Then for every index point the following data is included:
1917              // Fraction at index (Fi)          $xx (xx)
1918  
1919              $frame_offset = 0;
1920              $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1921              $frame_offset += 4;
1922              $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1923              $frame_offset += 4;
1924              $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1925              $frame_offset += 2;
1926              $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1927              $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1928              for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1929                  $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1930                  $frame_offset += $frame_bytesperpoint;
1931              }
1932              unset($parsedFrame['data']);
1933  
1934          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1935              // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1936              //   There may only be one 'RGAD' frame in a tag
1937              // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1938              // Peak Amplitude                      $xx $xx $xx $xx
1939              // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1940              // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1941              //   a - name code
1942              //   b - originator code
1943              //   c - sign bit
1944              //   d - replay gain adjustment
1945  
1946              $frame_offset = 0;
1947              $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1948              $frame_offset += 4;
1949              $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1950              $frame_offset += 2;
1951              $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1952              $frame_offset += 2;
1953              $parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1954              $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1955              $parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1956              $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1957              $parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1958              $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1959              $parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1960              $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1961              $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1962              $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1963              $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1964              $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1965              $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1966              $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1967  
1968              $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
1969              $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1970              $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1971              $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1972              $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1973  
1974              unset($parsedFrame['data']);
1975  
1976          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
1977              // http://id3.org/id3v2-chapters-1.0
1978              // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
1979              // Element ID      <text string> $00
1980              // Start time      $xx xx xx xx
1981              // End time        $xx xx xx xx
1982              // Start offset    $xx xx xx xx
1983              // End offset      $xx xx xx xx
1984              // <Optional embedded sub-frames>
1985  
1986              $frame_offset = 0;
1987              @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
1988              $frame_offset += strlen($parsedFrame['element_id']."\x00");
1989              $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1990              $frame_offset += 4;
1991              $parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1992              $frame_offset += 4;
1993              if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
1994                  // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
1995                  $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1996              }
1997              $frame_offset += 4;
1998              if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
1999                  // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2000                  $parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2001              }
2002              $frame_offset += 4;
2003  
2004              if ($frame_offset < strlen($parsedFrame['data'])) {
2005                  $parsedFrame['subframes'] = array();
2006                  while ($frame_offset < strlen($parsedFrame['data'])) {
2007                      // <Optional embedded sub-frames>
2008                      $subframe = array();
2009                      $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2010                      $frame_offset += 4;
2011                      $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2012                      $frame_offset += 4;
2013                      $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2014                      $frame_offset += 2;
2015                      if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2016                          $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2017                          break;
2018                      }
2019                      $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2020                      $frame_offset += $subframe['size'];
2021  
2022                      $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2023                      $subframe['text']       =     substr($subframe_rawdata, 1);
2024                      $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2025                      $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
2026                      switch (substr($encoding_converted_text, 0, 2)) {
2027                          case "\xFF\xFE":
2028                          case "\xFE\xFF":
2029                              switch (strtoupper($info['id3v2']['encoding'])) {
2030                                  case 'ISO-8859-1':
2031                                  case 'UTF-8':
2032                                      $encoding_converted_text = substr($encoding_converted_text, 2);
2033                                      // remove unwanted byte-order-marks
2034                                      break;
2035                                  default:
2036                                      // ignore
2037                                      break;
2038                              }
2039                              break;
2040                          default:
2041                              // do not remove BOM
2042                              break;
2043                      }
2044  
2045                      switch ($subframe['name']) {
2046                          case 'TIT2':
2047                              $parsedFrame['chapter_name']        = $encoding_converted_text;
2048                              $parsedFrame['subframes'][] = $subframe;
2049                              break;
2050                          case 'TIT3':
2051                              $parsedFrame['chapter_description'] = $encoding_converted_text;
2052                              $parsedFrame['subframes'][] = $subframe;
2053                              break;
2054                          case 'WXXX':
2055                              list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
2056                              $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
2057                              $parsedFrame['subframes'][] = $subframe;
2058                              break;
2059                          case 'APIC':
2060                              if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
2061                                  list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
2062                                  $subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
2063                                  $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
2064                                  $subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
2065                                  if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
2066                                      // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
2067                                      // the above regex assumes one byte, if it's actually two then strip the second one here
2068                                      $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
2069                                  }
2070                                  $subframe['data'] = $subframe_apic_picturedata;
2071                                  unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
2072                                  unset($subframe['text'], $parsedFrame['text']);
2073                                  $parsedFrame['subframes'][] = $subframe;
2074                                  $parsedFrame['picture_present'] = true;
2075                              } else {
2076                                  $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
2077                              }
2078                              break;
2079                          default:
2080                              $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
2081                              break;
2082                      }
2083                  }
2084                  unset($subframe_rawdata, $subframe, $encoding_converted_text);
2085                  unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
2086              }
2087  
2088              $id3v2_chapter_entry = array();
2089              foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
2090                  if (isset($parsedFrame[$id3v2_chapter_key])) {
2091                      $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2092                  }
2093              }
2094              if (!isset($info['id3v2']['chapters'])) {
2095                  $info['id3v2']['chapters'] = array();
2096              }
2097              $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2098              unset($id3v2_chapter_entry, $id3v2_chapter_key);
2099  
2100  
2101          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2102              // http://id3.org/id3v2-chapters-1.0
2103              // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
2104              // Element ID      <text string> $00
2105              // CTOC flags        %xx
2106              // Entry count       $xx
2107              // Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
2108              // <Optional embedded sub-frames>
2109  
2110              $frame_offset = 0;
2111              @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2112              $frame_offset += strlen($parsedFrame['element_id']."\x00");
2113              $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2114              $frame_offset += 1;
2115              $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2116              $frame_offset += 1;
2117  
2118              $terminator_position = null;
2119              for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
2120                  $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2121                  $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2122                  $frame_offset = $terminator_position + 1;
2123              }
2124  
2125              $parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
2126              $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2127  
2128              unset($ctoc_flags_raw, $terminator_position);
2129  
2130              if ($frame_offset < strlen($parsedFrame['data'])) {
2131                  $parsedFrame['subframes'] = array();
2132                  while ($frame_offset < strlen($parsedFrame['data'])) {
2133                      // <Optional embedded sub-frames>
2134                      $subframe = array();
2135                      $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2136                      $frame_offset += 4;
2137                      $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2138                      $frame_offset += 4;
2139                      $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2140                      $frame_offset += 2;
2141                      if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2142                          $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2143                          break;
2144                      }
2145                      $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2146                      $frame_offset += $subframe['size'];
2147  
2148                      $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2149                      $subframe['text']       =     substr($subframe_rawdata, 1);
2150                      $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2151                      $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2152                      switch (substr($encoding_converted_text, 0, 2)) {
2153                          case "\xFF\xFE":
2154                          case "\xFE\xFF":
2155                              switch (strtoupper($info['id3v2']['encoding'])) {
2156                                  case 'ISO-8859-1':
2157                                  case 'UTF-8':
2158                                      $encoding_converted_text = substr($encoding_converted_text, 2);
2159                                      // remove unwanted byte-order-marks
2160                                      break;
2161                                  default:
2162                                      // ignore
2163                                      break;
2164                              }
2165                              break;
2166                          default:
2167                              // do not remove BOM
2168                              break;
2169                      }
2170  
2171                      if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2172                          if ($subframe['name'] == 'TIT2') {
2173                              $parsedFrame['toc_name']        = $encoding_converted_text;
2174                          } elseif ($subframe['name'] == 'TIT3') {
2175                              $parsedFrame['toc_description'] = $encoding_converted_text;
2176                          }
2177                          $parsedFrame['subframes'][] = $subframe;
2178                      } else {
2179                          $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2180                      }
2181                  }
2182                  unset($subframe_rawdata, $subframe, $encoding_converted_text);
2183              }
2184  
2185          }
2186  
2187          return true;
2188      }
2189  
2190      /**
2191       * @param string $data
2192       *
2193       * @return string
2194       */
2195  	public function DeUnsynchronise($data) {
2196          return str_replace("\xFF\x00", "\xFF", $data);
2197      }
2198  
2199      /**
2200       * @param int $index
2201       *
2202       * @return string
2203       */
2204  	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2205          static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2206              0x00 => 'No more than 128 frames and 1 MB total tag size',
2207              0x01 => 'No more than 64 frames and 128 KB total tag size',
2208              0x02 => 'No more than 32 frames and 40 KB total tag size',
2209              0x03 => 'No more than 32 frames and 4 KB total tag size',
2210          );
2211          return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2212      }
2213  
2214      /**
2215       * @param int $index
2216       *
2217       * @return string
2218       */
2219  	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2220          static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2221              0x00 => 'No restrictions',
2222              0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2223          );
2224          return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2225      }
2226  
2227      /**
2228       * @param int $index
2229       *
2230       * @return string
2231       */
2232  	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2233          static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2234              0x00 => 'No restrictions',
2235              0x01 => 'No string is longer than 1024 characters',
2236              0x02 => 'No string is longer than 128 characters',
2237              0x03 => 'No string is longer than 30 characters',
2238          );
2239          return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2240      }
2241  
2242      /**
2243       * @param int $index
2244       *
2245       * @return string
2246       */
2247  	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2248          static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2249              0x00 => 'No restrictions',
2250              0x01 => 'Images are encoded only with PNG or JPEG',
2251          );
2252          return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2253      }
2254  
2255      /**
2256       * @param int $index
2257       *
2258       * @return string
2259       */
2260  	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2261          static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2262              0x00 => 'No restrictions',
2263              0x01 => 'All images are 256x256 pixels or smaller',
2264              0x02 => 'All images are 64x64 pixels or smaller',
2265              0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2266          );
2267          return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2268      }
2269  
2270      /**
2271       * @param string $currencyid
2272       *
2273       * @return string
2274       */
2275  	public function LookupCurrencyUnits($currencyid) {
2276  
2277          $begin = __LINE__;
2278  
2279          /** This is not a comment!
2280  
2281  
2282              AED    Dirhams
2283              AFA    Afghanis
2284              ALL    Leke
2285              AMD    Drams
2286              ANG    Guilders
2287              AOA    Kwanza
2288              ARS    Pesos
2289              ATS    Schillings
2290              AUD    Dollars
2291              AWG    Guilders
2292              AZM    Manats
2293              BAM    Convertible Marka
2294              BBD    Dollars
2295              BDT    Taka
2296              BEF    Francs
2297              BGL    Leva
2298              BHD    Dinars
2299              BIF    Francs
2300              BMD    Dollars
2301              BND    Dollars
2302              BOB    Bolivianos
2303              BRL    Brazil Real
2304              BSD    Dollars
2305              BTN    Ngultrum
2306              BWP    Pulas
2307              BYR    Rubles
2308              BZD    Dollars
2309              CAD    Dollars
2310              CDF    Congolese Francs
2311              CHF    Francs
2312              CLP    Pesos
2313              CNY    Yuan Renminbi
2314              COP    Pesos
2315              CRC    Colones
2316              CUP    Pesos
2317              CVE    Escudos
2318              CYP    Pounds
2319              CZK    Koruny
2320              DEM    Deutsche Marks
2321              DJF    Francs
2322              DKK    Kroner
2323              DOP    Pesos
2324              DZD    Algeria Dinars
2325              EEK    Krooni
2326              EGP    Pounds
2327              ERN    Nakfa
2328              ESP    Pesetas
2329              ETB    Birr
2330              EUR    Euro
2331              FIM    Markkaa
2332              FJD    Dollars
2333              FKP    Pounds
2334              FRF    Francs
2335              GBP    Pounds
2336              GEL    Lari
2337              GGP    Pounds
2338              GHC    Cedis
2339              GIP    Pounds
2340              GMD    Dalasi
2341              GNF    Francs
2342              GRD    Drachmae
2343              GTQ    Quetzales
2344              GYD    Dollars
2345              HKD    Dollars
2346              HNL    Lempiras
2347              HRK    Kuna
2348              HTG    Gourdes
2349              HUF    Forints
2350              IDR    Rupiahs
2351              IEP    Pounds
2352              ILS    New Shekels
2353              IMP    Pounds
2354              INR    Rupees
2355              IQD    Dinars
2356              IRR    Rials
2357              ISK    Kronur
2358              ITL    Lire
2359              JEP    Pounds
2360              JMD    Dollars
2361              JOD    Dinars
2362              JPY    Yen
2363              KES    Shillings
2364              KGS    Soms
2365              KHR    Riels
2366              KMF    Francs
2367              KPW    Won
2368              KWD    Dinars
2369              KYD    Dollars
2370              KZT    Tenge
2371              LAK    Kips
2372              LBP    Pounds
2373              LKR    Rupees
2374              LRD    Dollars
2375              LSL    Maloti
2376              LTL    Litai
2377              LUF    Francs
2378              LVL    Lati
2379              LYD    Dinars
2380              MAD    Dirhams
2381              MDL    Lei
2382              MGF    Malagasy Francs
2383              MKD    Denars
2384              MMK    Kyats
2385              MNT    Tugriks
2386              MOP    Patacas
2387              MRO    Ouguiyas
2388              MTL    Liri
2389              MUR    Rupees
2390              MVR    Rufiyaa
2391              MWK    Kwachas
2392              MXN    Pesos
2393              MYR    Ringgits
2394              MZM    Meticais
2395              NAD    Dollars
2396              NGN    Nairas
2397              NIO    Gold Cordobas
2398              NLG    Guilders
2399              NOK    Krone
2400              NPR    Nepal Rupees
2401              NZD    Dollars
2402              OMR    Rials
2403              PAB    Balboa
2404              PEN    Nuevos Soles
2405              PGK    Kina
2406              PHP    Pesos
2407              PKR    Rupees
2408              PLN    Zlotych
2409              PTE    Escudos
2410              PYG    Guarani
2411              QAR    Rials
2412              ROL    Lei
2413              RUR    Rubles
2414              RWF    Rwanda Francs
2415              SAR    Riyals
2416              SBD    Dollars
2417              SCR    Rupees
2418              SDD    Dinars
2419              SEK    Kronor
2420              SGD    Dollars
2421              SHP    Pounds
2422              SIT    Tolars
2423              SKK    Koruny
2424              SLL    Leones
2425              SOS    Shillings
2426              SPL    Luigini
2427              SRG    Guilders
2428              STD    Dobras
2429              SVC    Colones
2430              SYP    Pounds
2431              SZL    Emalangeni
2432              THB    Baht
2433              TJR    Rubles
2434              TMM    Manats
2435              TND    Dinars
2436              TOP    Pa'anga
2437              TRL    Liras
2438              TTD    Dollars
2439              TVD    Tuvalu Dollars
2440              TWD    New Dollars
2441              TZS    Shillings
2442              UAH    Hryvnia
2443              UGX    Shillings
2444              USD    Dollars
2445              UYU    Pesos
2446              UZS    Sums
2447              VAL    Lire
2448              VEB    Bolivares
2449              VND    Dong
2450              VUV    Vatu
2451              WST    Tala
2452              XAF    Francs
2453              XAG    Ounces
2454              XAU    Ounces
2455              XCD    Dollars
2456              XDR    Special Drawing Rights
2457              XPD    Ounces
2458              XPF    Francs
2459              XPT    Ounces
2460              YER    Rials
2461              YUM    New Dinars
2462              ZAR    Rand
2463              ZMK    Kwacha
2464              ZWD    Zimbabwe Dollars
2465  
2466          */
2467  
2468          return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2469      }
2470  
2471      /**
2472       * @param string $currencyid
2473       *
2474       * @return string
2475       */
2476  	public function LookupCurrencyCountry($currencyid) {
2477  
2478          $begin = __LINE__;
2479  
2480          /** This is not a comment!
2481  
2482              AED    United Arab Emirates
2483              AFA    Afghanistan
2484              ALL    Albania
2485              AMD    Armenia
2486              ANG    Netherlands Antilles
2487              AOA    Angola
2488              ARS    Argentina
2489              ATS    Austria
2490              AUD    Australia
2491              AWG    Aruba
2492              AZM    Azerbaijan
2493              BAM    Bosnia and Herzegovina
2494              BBD    Barbados
2495              BDT    Bangladesh
2496              BEF    Belgium
2497              BGL    Bulgaria
2498              BHD    Bahrain
2499              BIF    Burundi
2500              BMD    Bermuda
2501              BND    Brunei Darussalam
2502              BOB    Bolivia
2503              BRL    Brazil
2504              BSD    Bahamas
2505              BTN    Bhutan
2506              BWP    Botswana
2507              BYR    Belarus
2508              BZD    Belize
2509              CAD    Canada
2510              CDF    Congo/Kinshasa
2511              CHF    Switzerland
2512              CLP    Chile
2513              CNY    China
2514              COP    Colombia
2515              CRC    Costa Rica
2516              CUP    Cuba
2517              CVE    Cape Verde
2518              CYP    Cyprus
2519              CZK    Czech Republic
2520              DEM    Germany
2521              DJF    Djibouti
2522              DKK    Denmark
2523              DOP    Dominican Republic
2524              DZD    Algeria
2525              EEK    Estonia
2526              EGP    Egypt
2527              ERN    Eritrea
2528              ESP    Spain
2529              ETB    Ethiopia
2530              EUR    Euro Member Countries
2531              FIM    Finland
2532              FJD    Fiji
2533              FKP    Falkland Islands (Malvinas)
2534              FRF    France
2535              GBP    United Kingdom
2536              GEL    Georgia
2537              GGP    Guernsey
2538              GHC    Ghana
2539              GIP    Gibraltar
2540              GMD    Gambia
2541              GNF    Guinea
2542              GRD    Greece
2543              GTQ    Guatemala
2544              GYD    Guyana
2545              HKD    Hong Kong
2546              HNL    Honduras
2547              HRK    Croatia
2548              HTG    Haiti
2549              HUF    Hungary
2550              IDR    Indonesia
2551              IEP    Ireland (Eire)
2552              ILS    Israel
2553              IMP    Isle of Man
2554              INR    India
2555              IQD    Iraq
2556              IRR    Iran
2557              ISK    Iceland
2558              ITL    Italy
2559              JEP    Jersey
2560              JMD    Jamaica
2561              JOD    Jordan
2562              JPY    Japan
2563              KES    Kenya
2564              KGS    Kyrgyzstan
2565              KHR    Cambodia
2566              KMF    Comoros
2567              KPW    Korea
2568              KWD    Kuwait
2569              KYD    Cayman Islands
2570              KZT    Kazakstan
2571              LAK    Laos
2572              LBP    Lebanon
2573              LKR    Sri Lanka
2574              LRD    Liberia
2575              LSL    Lesotho
2576              LTL    Lithuania
2577              LUF    Luxembourg
2578              LVL    Latvia
2579              LYD    Libya
2580              MAD    Morocco
2581              MDL    Moldova
2582              MGF    Madagascar
2583              MKD    Macedonia
2584              MMK    Myanmar (Burma)
2585              MNT    Mongolia
2586              MOP    Macau
2587              MRO    Mauritania
2588              MTL    Malta
2589              MUR    Mauritius
2590              MVR    Maldives (Maldive Islands)
2591              MWK    Malawi
2592              MXN    Mexico
2593              MYR    Malaysia
2594              MZM    Mozambique
2595              NAD    Namibia
2596              NGN    Nigeria
2597              NIO    Nicaragua
2598              NLG    Netherlands (Holland)
2599              NOK    Norway
2600              NPR    Nepal
2601              NZD    New Zealand
2602              OMR    Oman
2603              PAB    Panama
2604              PEN    Peru
2605              PGK    Papua New Guinea
2606              PHP    Philippines
2607              PKR    Pakistan
2608              PLN    Poland
2609              PTE    Portugal
2610              PYG    Paraguay
2611              QAR    Qatar
2612              ROL    Romania
2613              RUR    Russia
2614              RWF    Rwanda
2615              SAR    Saudi Arabia
2616              SBD    Solomon Islands
2617              SCR    Seychelles
2618              SDD    Sudan
2619              SEK    Sweden
2620              SGD    Singapore
2621              SHP    Saint Helena
2622              SIT    Slovenia
2623              SKK    Slovakia
2624              SLL    Sierra Leone
2625              SOS    Somalia
2626              SPL    Seborga
2627              SRG    Suriname
2628              STD    São Tome and Principe
2629              SVC    El Salvador
2630              SYP    Syria
2631              SZL    Swaziland
2632              THB    Thailand
2633              TJR    Tajikistan
2634              TMM    Turkmenistan
2635              TND    Tunisia
2636              TOP    Tonga
2637              TRL    Turkey
2638              TTD    Trinidad and Tobago
2639              TVD    Tuvalu
2640              TWD    Taiwan
2641              TZS    Tanzania
2642              UAH    Ukraine
2643              UGX    Uganda
2644              USD    United States of America
2645              UYU    Uruguay
2646              UZS    Uzbekistan
2647              VAL    Vatican City
2648              VEB    Venezuela
2649              VND    Viet Nam
2650              VUV    Vanuatu
2651              WST    Samoa
2652              XAF    Communauté Financière Africaine
2653              XAG    Silver
2654              XAU    Gold
2655              XCD    East Caribbean
2656              XDR    International Monetary Fund
2657              XPD    Palladium
2658              XPF    Comptoirs Français du Pacifique
2659              XPT    Platinum
2660              YER    Yemen
2661              YUM    Yugoslavia
2662              ZAR    South Africa
2663              ZMK    Zambia
2664              ZWD    Zimbabwe
2665  
2666          */
2667  
2668          return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2669      }
2670  
2671      /**
2672       * @param string $languagecode
2673       * @param bool   $casesensitive
2674       *
2675       * @return string
2676       */
2677  	public static function LanguageLookup($languagecode, $casesensitive=false) {
2678  
2679          if (!$casesensitive) {
2680              $languagecode = strtolower($languagecode);
2681          }
2682  
2683          // http://www.id3.org/id3v2.4.0-structure.txt
2684          // [4.   ID3v2 frame overview]
2685          // The three byte language field, present in several frames, is used to
2686          // describe the language of the frame's content, according to ISO-639-2
2687          // [ISO-639-2]. The language should be represented in lower case. If the
2688          // language is not known the string "XXX" should be used.
2689  
2690  
2691          // ISO 639-2 - http://www.id3.org/iso639-2.html
2692  
2693          $begin = __LINE__;
2694  
2695          /** This is not a comment!
2696  
2697              XXX    unknown
2698              xxx    unknown
2699              aar    Afar
2700              abk    Abkhazian
2701              ace    Achinese
2702              ach    Acoli
2703              ada    Adangme
2704              afa    Afro-Asiatic (Other)
2705              afh    Afrihili
2706              afr    Afrikaans
2707              aka    Akan
2708              akk    Akkadian
2709              alb    Albanian
2710              ale    Aleut
2711              alg    Algonquian Languages
2712              amh    Amharic
2713              ang    English, Old (ca. 450-1100)
2714              apa    Apache Languages
2715              ara    Arabic
2716              arc    Aramaic
2717              arm    Armenian
2718              arn    Araucanian
2719              arp    Arapaho
2720              art    Artificial (Other)
2721              arw    Arawak
2722              asm    Assamese
2723              ath    Athapascan Languages
2724              ava    Avaric
2725              ave    Avestan
2726              awa    Awadhi
2727              aym    Aymara
2728              aze    Azerbaijani
2729              bad    Banda
2730              bai    Bamileke Languages
2731              bak    Bashkir
2732              bal    Baluchi
2733              bam    Bambara
2734              ban    Balinese
2735              baq    Basque
2736              bas    Basa
2737              bat    Baltic (Other)
2738              bej    Beja
2739              bel    Byelorussian
2740              bem    Bemba
2741              ben    Bengali
2742              ber    Berber (Other)
2743              bho    Bhojpuri
2744              bih    Bihari
2745              bik    Bikol
2746              bin    Bini
2747              bis    Bislama
2748              bla    Siksika
2749              bnt    Bantu (Other)
2750              bod    Tibetan
2751              bra    Braj
2752              bre    Breton
2753              bua    Buriat
2754              bug    Buginese
2755              bul    Bulgarian
2756              bur    Burmese
2757              cad    Caddo
2758              cai    Central American Indian (Other)
2759              car    Carib
2760              cat    Catalan
2761              cau    Caucasian (Other)
2762              ceb    Cebuano
2763              cel    Celtic (Other)
2764              ces    Czech
2765              cha    Chamorro
2766              chb    Chibcha
2767              che    Chechen
2768              chg    Chagatai
2769              chi    Chinese
2770              chm    Mari
2771              chn    Chinook jargon
2772              cho    Choctaw
2773              chr    Cherokee
2774              chu    Church Slavic
2775              chv    Chuvash
2776              chy    Cheyenne
2777              cop    Coptic
2778              cor    Cornish
2779              cos    Corsican
2780              cpe    Creoles and Pidgins, English-based (Other)
2781              cpf    Creoles and Pidgins, French-based (Other)
2782              cpp    Creoles and Pidgins, Portuguese-based (Other)
2783              cre    Cree
2784              crp    Creoles and Pidgins (Other)
2785              cus    Cushitic (Other)
2786              cym    Welsh
2787              cze    Czech
2788              dak    Dakota
2789              dan    Danish
2790              del    Delaware
2791              deu    German
2792              din    Dinka
2793              div    Divehi
2794              doi    Dogri
2795              dra    Dravidian (Other)
2796              dua    Duala
2797              dum    Dutch, Middle (ca. 1050-1350)
2798              dut    Dutch
2799              dyu    Dyula
2800              dzo    Dzongkha
2801              efi    Efik
2802              egy    Egyptian (Ancient)
2803              eka    Ekajuk
2804              ell    Greek, Modern (1453-)
2805              elx    Elamite
2806              eng    English
2807              enm    English, Middle (ca. 1100-1500)
2808              epo    Esperanto
2809              esk    Eskimo (Other)
2810              esl    Spanish
2811              est    Estonian
2812              eus    Basque
2813              ewe    Ewe
2814              ewo    Ewondo
2815              fan    Fang
2816              fao    Faroese
2817              fas    Persian
2818              fat    Fanti
2819              fij    Fijian
2820              fin    Finnish
2821              fiu    Finno-Ugrian (Other)
2822              fon    Fon
2823              fra    French
2824              fre    French
2825              frm    French, Middle (ca. 1400-1600)
2826              fro    French, Old (842- ca. 1400)
2827              fry    Frisian
2828              ful    Fulah
2829              gaa    Ga
2830              gae    Gaelic (Scots)
2831              gai    Irish
2832              gay    Gayo
2833              gdh    Gaelic (Scots)
2834              gem    Germanic (Other)
2835              geo    Georgian
2836              ger    German
2837              gez    Geez
2838              gil    Gilbertese
2839              glg    Gallegan
2840              gmh    German, Middle High (ca. 1050-1500)
2841              goh    German, Old High (ca. 750-1050)
2842              gon    Gondi
2843              got    Gothic
2844              grb    Grebo
2845              grc    Greek, Ancient (to 1453)
2846              gre    Greek, Modern (1453-)
2847              grn    Guarani
2848              guj    Gujarati
2849              hai    Haida
2850              hau    Hausa
2851              haw    Hawaiian
2852              heb    Hebrew
2853              her    Herero
2854              hil    Hiligaynon
2855              him    Himachali
2856              hin    Hindi
2857              hmo    Hiri Motu
2858              hun    Hungarian
2859              hup    Hupa
2860              hye    Armenian
2861              iba    Iban
2862              ibo    Igbo
2863              ice    Icelandic
2864              ijo    Ijo
2865              iku    Inuktitut
2866              ilo    Iloko
2867              ina    Interlingua (International Auxiliary language Association)
2868              inc    Indic (Other)
2869              ind    Indonesian
2870              ine    Indo-European (Other)
2871              ine    Interlingue
2872              ipk    Inupiak
2873              ira    Iranian (Other)
2874              iri    Irish
2875              iro    Iroquoian uages
2876              isl    Icelandic
2877              ita    Italian
2878              jav    Javanese
2879              jaw    Javanese
2880              jpn    Japanese
2881              jpr    Judeo-Persian
2882              jrb    Judeo-Arabic
2883              kaa    Kara-Kalpak
2884              kab    Kabyle
2885              kac    Kachin
2886              kal    Greenlandic
2887              kam    Kamba
2888              kan    Kannada
2889              kar    Karen
2890              kas    Kashmiri
2891              kat    Georgian
2892              kau    Kanuri
2893              kaw    Kawi
2894              kaz    Kazakh
2895              kha    Khasi
2896              khi    Khoisan (Other)
2897              khm    Khmer
2898              kho    Khotanese
2899              kik    Kikuyu
2900              kin    Kinyarwanda
2901              kir    Kirghiz
2902              kok    Konkani
2903              kom    Komi
2904              kon    Kongo
2905              kor    Korean
2906              kpe    Kpelle
2907              kro    Kru
2908              kru    Kurukh
2909              kua    Kuanyama
2910              kum    Kumyk
2911              kur    Kurdish
2912              kus    Kusaie
2913              kut    Kutenai
2914              lad    Ladino
2915              lah    Lahnda
2916              lam    Lamba
2917              lao    Lao
2918              lat    Latin
2919              lav    Latvian
2920              lez    Lezghian
2921              lin    Lingala
2922              lit    Lithuanian
2923              lol    Mongo
2924              loz    Lozi
2925              ltz    Letzeburgesch
2926              lub    Luba-Katanga
2927              lug    Ganda
2928              lui    Luiseno
2929              lun    Lunda
2930              luo    Luo (Kenya and Tanzania)
2931              mac    Macedonian
2932              mad    Madurese
2933              mag    Magahi
2934              mah    Marshall
2935              mai    Maithili
2936              mak    Macedonian
2937              mak    Makasar
2938              mal    Malayalam
2939              man    Mandingo
2940              mao    Maori
2941              map    Austronesian (Other)
2942              mar    Marathi
2943              mas    Masai
2944              max    Manx
2945              may    Malay
2946              men    Mende
2947              mga    Irish, Middle (900 - 1200)
2948              mic    Micmac
2949              min    Minangkabau
2950              mis    Miscellaneous (Other)
2951              mkh    Mon-Kmer (Other)
2952              mlg    Malagasy
2953              mlt    Maltese
2954              mni    Manipuri
2955              mno    Manobo Languages
2956              moh    Mohawk
2957              mol    Moldavian
2958              mon    Mongolian
2959              mos    Mossi
2960              mri    Maori
2961              msa    Malay
2962              mul    Multiple Languages
2963              mun    Munda Languages
2964              mus    Creek
2965              mwr    Marwari
2966              mya    Burmese
2967              myn    Mayan Languages
2968              nah    Aztec
2969              nai    North American Indian (Other)
2970              nau    Nauru
2971              nav    Navajo
2972              nbl    Ndebele, South
2973              nde    Ndebele, North
2974              ndo    Ndongo
2975              nep    Nepali
2976              new    Newari
2977              nic    Niger-Kordofanian (Other)
2978              niu    Niuean
2979              nla    Dutch
2980              nno    Norwegian (Nynorsk)
2981              non    Norse, Old
2982              nor    Norwegian
2983              nso    Sotho, Northern
2984              nub    Nubian Languages
2985              nya    Nyanja
2986              nym    Nyamwezi
2987              nyn    Nyankole
2988              nyo    Nyoro
2989              nzi    Nzima
2990              oci    Langue d'Oc (post 1500)
2991              oji    Ojibwa
2992              ori    Oriya
2993              orm    Oromo
2994              osa    Osage
2995              oss    Ossetic
2996              ota    Turkish, Ottoman (1500 - 1928)
2997              oto    Otomian Languages
2998              paa    Papuan-Australian (Other)
2999              pag    Pangasinan
3000              pal    Pahlavi
3001              pam    Pampanga
3002              pan    Panjabi
3003              pap    Papiamento
3004              pau    Palauan
3005              peo    Persian, Old (ca 600 - 400 B.C.)
3006              per    Persian
3007              phn    Phoenician
3008              pli    Pali
3009              pol    Polish
3010              pon    Ponape
3011              por    Portuguese
3012              pra    Prakrit uages
3013              pro    Provencal, Old (to 1500)
3014              pus    Pushto
3015              que    Quechua
3016              raj    Rajasthani
3017              rar    Rarotongan
3018              roa    Romance (Other)
3019              roh    Rhaeto-Romance
3020              rom    Romany
3021              ron    Romanian
3022              rum    Romanian
3023              run    Rundi
3024              rus    Russian
3025              sad    Sandawe
3026              sag    Sango
3027              sah    Yakut
3028              sai    South American Indian (Other)
3029              sal    Salishan Languages
3030              sam    Samaritan Aramaic
3031              san    Sanskrit
3032              sco    Scots
3033              scr    Serbo-Croatian
3034              sel    Selkup
3035              sem    Semitic (Other)
3036              sga    Irish, Old (to 900)
3037              shn    Shan
3038              sid    Sidamo
3039              sin    Singhalese
3040              sio    Siouan Languages
3041              sit    Sino-Tibetan (Other)
3042              sla    Slavic (Other)
3043              slk    Slovak
3044              slo    Slovak
3045              slv    Slovenian
3046              smi    Sami Languages
3047              smo    Samoan
3048              sna    Shona
3049              snd    Sindhi
3050              sog    Sogdian
3051              som    Somali
3052              son    Songhai
3053              sot    Sotho, Southern
3054              spa    Spanish
3055              sqi    Albanian
3056              srd    Sardinian
3057              srr    Serer
3058              ssa    Nilo-Saharan (Other)
3059              ssw    Siswant
3060              ssw    Swazi
3061              suk    Sukuma
3062              sun    Sudanese
3063              sus    Susu
3064              sux    Sumerian
3065              sve    Swedish
3066              swa    Swahili
3067              swe    Swedish
3068              syr    Syriac
3069              tah    Tahitian
3070              tam    Tamil
3071              tat    Tatar
3072              tel    Telugu
3073              tem    Timne
3074              ter    Tereno
3075              tgk    Tajik
3076              tgl    Tagalog
3077              tha    Thai
3078              tib    Tibetan
3079              tig    Tigre
3080              tir    Tigrinya
3081              tiv    Tivi
3082              tli    Tlingit
3083              tmh    Tamashek
3084              tog    Tonga (Nyasa)
3085              ton    Tonga (Tonga Islands)
3086              tru    Truk
3087              tsi    Tsimshian
3088              tsn    Tswana
3089              tso    Tsonga
3090              tuk    Turkmen
3091              tum    Tumbuka
3092              tur    Turkish
3093              tut    Altaic (Other)
3094              twi    Twi
3095              tyv    Tuvinian
3096              uga    Ugaritic
3097              uig    Uighur
3098              ukr    Ukrainian
3099              umb    Umbundu
3100              und    Undetermined
3101              urd    Urdu
3102              uzb    Uzbek
3103              vai    Vai
3104              ven    Venda
3105              vie    Vietnamese
3106              vol    Volapük
3107              vot    Votic
3108              wak    Wakashan Languages
3109              wal    Walamo
3110              war    Waray
3111              was    Washo
3112              wel    Welsh
3113              wen    Sorbian Languages
3114              wol    Wolof
3115              xho    Xhosa
3116              yao    Yao
3117              yap    Yap
3118              yid    Yiddish
3119              yor    Yoruba
3120              zap    Zapotec
3121              zen    Zenaga
3122              zha    Zhuang
3123              zho    Chinese
3124              zul    Zulu
3125              zun    Zuni
3126  
3127          */
3128  
3129          return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
3130      }
3131  
3132      /**
3133       * @param int $index
3134       *
3135       * @return string
3136       */
3137  	public static function ETCOEventLookup($index) {
3138          if (($index >= 0x17) && ($index <= 0xDF)) {
3139              return 'reserved for future use';
3140          }
3141          if (($index >= 0xE0) && ($index <= 0xEF)) {
3142              return 'not predefined synch 0-F';
3143          }
3144          if (($index >= 0xF0) && ($index <= 0xFC)) {
3145              return 'reserved for future use';
3146          }
3147  
3148          static $EventLookup = array(
3149              0x00 => 'padding (has no meaning)',
3150              0x01 => 'end of initial silence',
3151              0x02 => 'intro start',
3152              0x03 => 'main part start',
3153              0x04 => 'outro start',
3154              0x05 => 'outro end',
3155              0x06 => 'verse start',
3156              0x07 => 'refrain start',
3157              0x08 => 'interlude start',
3158              0x09 => 'theme start',
3159              0x0A => 'variation start',
3160              0x0B => 'key change',
3161              0x0C => 'time change',
3162              0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3163              0x0E => 'sustained noise',
3164              0x0F => 'sustained noise end',
3165              0x10 => 'intro end',
3166              0x11 => 'main part end',
3167              0x12 => 'verse end',
3168              0x13 => 'refrain end',
3169              0x14 => 'theme end',
3170              0x15 => 'profanity',
3171              0x16 => 'profanity end',
3172              0xFD => 'audio end (start of silence)',
3173              0xFE => 'audio file ends',
3174              0xFF => 'one more byte of events follows'
3175          );
3176  
3177          return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
3178      }
3179  
3180      /**
3181       * @param int $index
3182       *
3183       * @return string
3184       */
3185  	public static function SYTLContentTypeLookup($index) {
3186          static $SYTLContentTypeLookup = array(
3187              0x00 => 'other',
3188              0x01 => 'lyrics',
3189              0x02 => 'text transcription',
3190              0x03 => 'movement/part name', // (e.g. 'Adagio')
3191              0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
3192              0x05 => 'chord',              // (e.g. 'Bb F Fsus')
3193              0x06 => 'trivia/\'pop up\' information',
3194              0x07 => 'URLs to webpages',
3195              0x08 => 'URLs to images'
3196          );
3197  
3198          return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
3199      }
3200  
3201      /**
3202       * @param int   $index
3203       * @param bool $returnarray
3204       *
3205       * @return array|string
3206       */
3207  	public static function APICPictureTypeLookup($index, $returnarray=false) {
3208          static $APICPictureTypeLookup = array(
3209              0x00 => 'Other',
3210              0x01 => '32x32 pixels \'file icon\' (PNG only)',
3211              0x02 => 'Other file icon',
3212              0x03 => 'Cover (front)',
3213              0x04 => 'Cover (back)',
3214              0x05 => 'Leaflet page',
3215              0x06 => 'Media (e.g. label side of CD)',
3216              0x07 => 'Lead artist/lead performer/soloist',
3217              0x08 => 'Artist/performer',
3218              0x09 => 'Conductor',
3219              0x0A => 'Band/Orchestra',
3220              0x0B => 'Composer',
3221              0x0C => 'Lyricist/text writer',
3222              0x0D => 'Recording Location',
3223              0x0E => 'During recording',
3224              0x0F => 'During performance',
3225              0x10 => 'Movie/video screen capture',
3226              0x11 => 'A bright coloured fish',
3227              0x12 => 'Illustration',
3228              0x13 => 'Band/artist logotype',
3229              0x14 => 'Publisher/Studio logotype'
3230          );
3231          if ($returnarray) {
3232              return $APICPictureTypeLookup;
3233          }
3234          return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
3235      }
3236  
3237      /**
3238       * @param int $index
3239       *
3240       * @return string
3241       */
3242  	public static function COMRReceivedAsLookup($index) {
3243          static $COMRReceivedAsLookup = array(
3244              0x00 => 'Other',
3245              0x01 => 'Standard CD album with other songs',
3246              0x02 => 'Compressed audio on CD',
3247              0x03 => 'File over the Internet',
3248              0x04 => 'Stream over the Internet',
3249              0x05 => 'As note sheets',
3250              0x06 => 'As note sheets in a book with other sheets',
3251              0x07 => 'Music on other media',
3252              0x08 => 'Non-musical merchandise'
3253          );
3254  
3255          return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
3256      }
3257  
3258      /**
3259       * @param int $index
3260       *
3261       * @return string
3262       */
3263  	public static function RVA2ChannelTypeLookup($index) {
3264          static $RVA2ChannelTypeLookup = array(
3265              0x00 => 'Other',
3266              0x01 => 'Master volume',
3267              0x02 => 'Front right',
3268              0x03 => 'Front left',
3269              0x04 => 'Back right',
3270              0x05 => 'Back left',
3271              0x06 => 'Front centre',
3272              0x07 => 'Back centre',
3273              0x08 => 'Subwoofer'
3274          );
3275  
3276          return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
3277      }
3278  
3279      /**
3280       * @param string $framename
3281       *
3282       * @return string
3283       */
3284  	public static function FrameNameLongLookup($framename) {
3285  
3286          $begin = __LINE__;
3287  
3288          /** This is not a comment!
3289  
3290              AENC    Audio encryption
3291              APIC    Attached picture
3292              ASPI    Audio seek point index
3293              BUF    Recommended buffer size
3294              CNT    Play counter
3295              COM    Comments
3296              COMM    Comments
3297              COMR    Commercial frame
3298              CRA    Audio encryption
3299              CRM    Encrypted meta frame
3300              ENCR    Encryption method registration
3301              EQU    Equalisation
3302              EQU2    Equalisation (2)
3303              EQUA    Equalisation
3304              ETC    Event timing codes
3305              ETCO    Event timing codes
3306              GEO    General encapsulated object
3307              GEOB    General encapsulated object
3308              GRID    Group identification registration
3309              IPL    Involved people list
3310              IPLS    Involved people list
3311              LINK    Linked information
3312              LNK    Linked information
3313              MCDI    Music CD identifier
3314              MCI    Music CD Identifier
3315              MLL    MPEG location lookup table
3316              MLLT    MPEG location lookup table
3317              OWNE    Ownership frame
3318              PCNT    Play counter
3319              PIC    Attached picture
3320              POP    Popularimeter
3321              POPM    Popularimeter
3322              POSS    Position synchronisation frame
3323              PRIV    Private frame
3324              RBUF    Recommended buffer size
3325              REV    Reverb
3326              RVA    Relative volume adjustment
3327              RVA2    Relative volume adjustment (2)
3328              RVAD    Relative volume adjustment
3329              RVRB    Reverb
3330              SEEK    Seek frame
3331              SIGN    Signature frame
3332              SLT    Synchronised lyric/text
3333              STC    Synced tempo codes
3334              SYLT    Synchronised lyric/text
3335              SYTC    Synchronised tempo codes
3336              TAL    Album/Movie/Show title
3337              TALB    Album/Movie/Show title
3338              TBP    BPM (Beats Per Minute)
3339              TBPM    BPM (beats per minute)
3340              TCM    Composer
3341              TCMP    Part of a compilation
3342              TCO    Content type
3343              TCOM    Composer
3344              TCON    Content type
3345              TCOP    Copyright message
3346              TCP    Part of a compilation
3347              TCR    Copyright message
3348              TDA    Date
3349              TDAT    Date
3350              TDEN    Encoding time
3351              TDLY    Playlist delay
3352              TDOR    Original release time
3353              TDRC    Recording time
3354              TDRL    Release time
3355              TDTG    Tagging time
3356              TDY    Playlist delay
3357              TEN    Encoded by
3358              TENC    Encoded by
3359              TEXT    Lyricist/Text writer
3360              TFLT    File type
3361              TFT    File type
3362              TIM    Time
3363              TIME    Time
3364              TIPL    Involved people list
3365              TIT1    Content group description
3366              TIT2    Title/songname/content description
3367              TIT3    Subtitle/Description refinement
3368              TKE    Initial key
3369              TKEY    Initial key
3370              TLA    Language(s)
3371              TLAN    Language(s)
3372              TLE    Length
3373              TLEN    Length
3374              TMCL    Musician credits list
3375              TMED    Media type
3376              TMOO    Mood
3377              TMT    Media type
3378              TOA    Original artist(s)/performer(s)
3379              TOAL    Original album/movie/show title
3380              TOF    Original filename
3381              TOFN    Original filename
3382              TOL    Original Lyricist(s)/text writer(s)
3383              TOLY    Original lyricist(s)/text writer(s)
3384              TOPE    Original artist(s)/performer(s)
3385              TOR    Original release year
3386              TORY    Original release year
3387              TOT    Original album/Movie/Show title
3388              TOWN    File owner/licensee
3389              TP1    Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3390              TP2    Band/Orchestra/Accompaniment
3391              TP3    Conductor/Performer refinement
3392              TP4    Interpreted, remixed, or otherwise modified by
3393              TPA    Part of a set
3394              TPB    Publisher
3395              TPE1    Lead performer(s)/Soloist(s)
3396              TPE2    Band/orchestra/accompaniment
3397              TPE3    Conductor/performer refinement
3398              TPE4    Interpreted, remixed, or otherwise modified by
3399              TPOS    Part of a set
3400              TPRO    Produced notice
3401              TPUB    Publisher
3402              TRC    ISRC (International Standard Recording Code)
3403              TRCK    Track number/Position in set
3404              TRD    Recording dates
3405              TRDA    Recording dates
3406              TRK    Track number/Position in set
3407              TRSN    Internet radio station name
3408              TRSO    Internet radio station owner
3409              TS2    Album-Artist sort order
3410              TSA    Album sort order
3411              TSC    Composer sort order
3412              TSI    Size
3413              TSIZ    Size
3414              TSO2    Album-Artist sort order
3415              TSOA    Album sort order
3416              TSOC    Composer sort order
3417              TSOP    Performer sort order
3418              TSOT    Title sort order
3419              TSP    Performer sort order
3420              TSRC    ISRC (international standard recording code)
3421              TSS    Software/hardware and settings used for encoding
3422              TSSE    Software/Hardware and settings used for encoding
3423              TSST    Set subtitle
3424              TST    Title sort order
3425              TT1    Content group description
3426              TT2    Title/Songname/Content description
3427              TT3    Subtitle/Description refinement
3428              TXT    Lyricist/text writer
3429              TXX    User defined text information frame
3430              TXXX    User defined text information frame
3431              TYE    Year
3432              TYER    Year
3433              UFI    Unique file identifier
3434              UFID    Unique file identifier
3435              ULT    Unsynchronised lyric/text transcription
3436              USER    Terms of use
3437              USLT    Unsynchronised lyric/text transcription
3438              WAF    Official audio file webpage
3439              WAR    Official artist/performer webpage
3440              WAS    Official audio source webpage
3441              WCM    Commercial information
3442              WCOM    Commercial information
3443              WCOP    Copyright/Legal information
3444              WCP    Copyright/Legal information
3445              WOAF    Official audio file webpage
3446              WOAR    Official artist/performer webpage
3447              WOAS    Official audio source webpage
3448              WORS    Official Internet radio station homepage
3449              WPAY    Payment
3450              WPB    Publishers official webpage
3451              WPUB    Publishers official webpage
3452              WXX    User defined URL link frame
3453              WXXX    User defined URL link frame
3454              TFEA    Featured Artist
3455              TSTU    Recording Studio
3456              rgad    Replay Gain Adjustment
3457  
3458          */
3459  
3460          return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3461  
3462          // Last three:
3463          // from Helium2 [www.helium2.com]
3464          // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3465      }
3466  
3467      /**
3468       * @param string $framename
3469       *
3470       * @return string
3471       */
3472  	public static function FrameNameShortLookup($framename) {
3473  
3474          $begin = __LINE__;
3475  
3476          /** This is not a comment!
3477  
3478              AENC    audio_encryption
3479              APIC    attached_picture
3480              ASPI    audio_seek_point_index
3481              BUF    recommended_buffer_size
3482              CNT    play_counter
3483              COM    comment
3484              COMM    comment
3485              COMR    commercial_frame
3486              CRA    audio_encryption
3487              CRM    encrypted_meta_frame
3488              ENCR    encryption_method_registration
3489              EQU    equalisation
3490              EQU2    equalisation
3491              EQUA    equalisation
3492              ETC    event_timing_codes
3493              ETCO    event_timing_codes
3494              GEO    general_encapsulated_object
3495              GEOB    general_encapsulated_object
3496              GRID    group_identification_registration
3497              IPL    involved_people_list
3498              IPLS    involved_people_list
3499              LINK    linked_information
3500              LNK    linked_information
3501              MCDI    music_cd_identifier
3502              MCI    music_cd_identifier
3503              MLL    mpeg_location_lookup_table
3504              MLLT    mpeg_location_lookup_table
3505              OWNE    ownership_frame
3506              PCNT    play_counter
3507              PIC    attached_picture
3508              POP    popularimeter
3509              POPM    popularimeter
3510              POSS    position_synchronisation_frame
3511              PRIV    private_frame
3512              RBUF    recommended_buffer_size
3513              REV    reverb
3514              RVA    relative_volume_adjustment
3515              RVA2    relative_volume_adjustment
3516              RVAD    relative_volume_adjustment
3517              RVRB    reverb
3518              SEEK    seek_frame
3519              SIGN    signature_frame
3520              SLT    synchronised_lyric
3521              STC    synced_tempo_codes
3522              SYLT    synchronised_lyric
3523              SYTC    synchronised_tempo_codes
3524              TAL    album
3525              TALB    album
3526              TBP    bpm
3527              TBPM    bpm
3528              TCM    composer
3529              TCMP    part_of_a_compilation
3530              TCO    genre
3531              TCOM    composer
3532              TCON    genre
3533              TCOP    copyright_message
3534              TCP    part_of_a_compilation
3535              TCR    copyright_message
3536              TDA    date
3537              TDAT    date
3538              TDEN    encoding_time
3539              TDLY    playlist_delay
3540              TDOR    original_release_time
3541              TDRC    recording_time
3542              TDRL    release_time
3543              TDTG    tagging_time
3544              TDY    playlist_delay
3545              TEN    encoded_by
3546              TENC    encoded_by
3547              TEXT    lyricist
3548              TFLT    file_type
3549              TFT    file_type
3550              TIM    time
3551              TIME    time
3552              TIPL    involved_people_list
3553              TIT1    content_group_description
3554              TIT2    title
3555              TIT3    subtitle
3556              TKE    initial_key
3557              TKEY    initial_key
3558              TLA    language
3559              TLAN    language
3560              TLE    length
3561              TLEN    length
3562              TMCL    musician_credits_list
3563              TMED    media_type
3564              TMOO    mood
3565              TMT    media_type
3566              TOA    original_artist
3567              TOAL    original_album
3568              TOF    original_filename
3569              TOFN    original_filename
3570              TOL    original_lyricist
3571              TOLY    original_lyricist
3572              TOPE    original_artist
3573              TOR    original_year
3574              TORY    original_year
3575              TOT    original_album
3576              TOWN    file_owner
3577              TP1    artist
3578              TP2    band
3579              TP3    conductor
3580              TP4    remixer
3581              TPA    part_of_a_set
3582              TPB    publisher
3583              TPE1    artist
3584              TPE2    band
3585              TPE3    conductor
3586              TPE4    remixer
3587              TPOS    part_of_a_set
3588              TPRO    produced_notice
3589              TPUB    publisher
3590              TRC    isrc
3591              TRCK    track_number
3592              TRD    recording_dates
3593              TRDA    recording_dates
3594              TRK    track_number
3595              TRSN    internet_radio_station_name
3596              TRSO    internet_radio_station_owner
3597              TS2    album_artist_sort_order
3598              TSA    album_sort_order
3599              TSC    composer_sort_order
3600              TSI    size
3601              TSIZ    size
3602              TSO2    album_artist_sort_order
3603              TSOA    album_sort_order
3604              TSOC    composer_sort_order
3605              TSOP    performer_sort_order
3606              TSOT    title_sort_order
3607              TSP    performer_sort_order
3608              TSRC    isrc
3609              TSS    encoder_settings
3610              TSSE    encoder_settings
3611              TSST    set_subtitle
3612              TST    title_sort_order
3613              TT1    content_group_description
3614              TT2    title
3615              TT3    subtitle
3616              TXT    lyricist
3617              TXX    text
3618              TXXX    text
3619              TYE    year
3620              TYER    year
3621              UFI    unique_file_identifier
3622              UFID    unique_file_identifier
3623              ULT    unsynchronised_lyric
3624              USER    terms_of_use
3625              USLT    unsynchronised_lyric
3626              WAF    url_file
3627              WAR    url_artist
3628              WAS    url_source
3629              WCM    commercial_information
3630              WCOM    commercial_information
3631              WCOP    copyright
3632              WCP    copyright
3633              WOAF    url_file
3634              WOAR    url_artist
3635              WOAS    url_source
3636              WORS    url_station
3637              WPAY    url_payment
3638              WPB    url_publisher
3639              WPUB    url_publisher
3640              WXX    url_user
3641              WXXX    url_user
3642              TFEA    featured_artist
3643              TSTU    recording_studio
3644              rgad    replay_gain_adjustment
3645  
3646          */
3647  
3648          return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3649      }
3650  
3651      /**
3652       * @param string $encoding
3653       *
3654       * @return string
3655       */
3656  	public static function TextEncodingTerminatorLookup($encoding) {
3657          // http://www.id3.org/id3v2.4.0-structure.txt
3658          // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3659          static $TextEncodingTerminatorLookup = array(
3660              0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
3661              1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3662              2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3663              3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
3664              255 => "\x00\x00"
3665          );
3666          return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
3667      }
3668  
3669      /**
3670       * @param int $encoding
3671       *
3672       * @return string
3673       */
3674  	public static function TextEncodingNameLookup($encoding) {
3675          // http://www.id3.org/id3v2.4.0-structure.txt
3676          // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3677          static $TextEncodingNameLookup = array(
3678              0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
3679              1   => 'UTF-16',     // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3680              2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3681              3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
3682              255 => 'UTF-16BE'
3683          );
3684          return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3685      }
3686  
3687      /**
3688       * @param string $string
3689       * @param string $terminator
3690       *
3691       * @return string
3692       */
3693  	public static function RemoveStringTerminator($string, $terminator) {
3694          // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
3695          // https://github.com/JamesHeinrich/getID3/issues/121
3696          // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
3697          if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
3698              $string = substr($string, 0, -strlen($terminator));
3699          }
3700          return $string;
3701      }
3702  
3703      /**
3704       * @param string $string
3705       *
3706       * @return string
3707       */
3708  	public static function MakeUTF16emptyStringEmpty($string) {
3709          if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
3710              // if string only contains a BOM or terminator then make it actually an empty string
3711              $string = '';
3712          }
3713          return $string;
3714      }
3715  
3716      /**
3717       * @param string $framename
3718       * @param int    $id3v2majorversion
3719       *
3720       * @return bool|int
3721       */
3722  	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3723          switch ($id3v2majorversion) {
3724              case 2:
3725                  return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3726                  break;
3727  
3728              case 3:
3729              case 4:
3730                  return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3731                  break;
3732          }
3733          return false;
3734      }
3735  
3736      /**
3737       * @param string $numberstring
3738       * @param bool   $allowdecimal
3739       * @param bool   $allownegative
3740       *
3741       * @return bool
3742       */
3743  	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3744          for ($i = 0; $i < strlen($numberstring); $i++) {
3745              if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
3746                  if (($numberstring[$i] == '.') && $allowdecimal) {
3747                      // allowed
3748                  } elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
3749                      // allowed
3750                  } else {
3751                      return false;
3752                  }
3753              }
3754          }
3755          return true;
3756      }
3757  
3758      /**
3759       * @param string $datestamp
3760       *
3761       * @return bool
3762       */
3763  	public static function IsValidDateStampString($datestamp) {
3764          if (strlen($datestamp) != 8) {
3765              return false;
3766          }
3767          if (!self::IsANumber($datestamp, false)) {
3768              return false;
3769          }
3770          $year  = substr($datestamp, 0, 4);
3771          $month = substr($datestamp, 4, 2);
3772          $day   = substr($datestamp, 6, 2);
3773          if (($year == 0) || ($month == 0) || ($day == 0)) {
3774              return false;
3775          }
3776          if ($month > 12) {
3777              return false;
3778          }
3779          if ($day > 31) {
3780              return false;
3781          }
3782          if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3783              return false;
3784          }
3785          if (($day > 29) && ($month == 2)) {
3786              return false;
3787          }
3788          return true;
3789      }
3790  
3791      /**
3792       * @param int $majorversion
3793       *
3794       * @return int
3795       */
3796  	public static function ID3v2HeaderLength($majorversion) {
3797          return (($majorversion == 2) ? 6 : 10);
3798      }
3799  
3800      /**
3801       * @param string $frame_name
3802       *
3803       * @return string|false
3804       */
3805  	public static function ID3v22iTunesBrokenFrameName($frame_name) {
3806          // iTunes (multiple versions) has been known to write ID3v2.3 style frames
3807          // but use ID3v2.2 frame names, right-padded using either [space] or [null]
3808          // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3809          // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3810          static $ID3v22_iTunes_BrokenFrames = array(
3811              'BUF' => 'RBUF', // Re