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