[ 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                      $parsedFrame                    = array();
 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              if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) {  // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
 998                  $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
 999                  $frame_offset += 3;
1000                  $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1001                  if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1002                      $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1003                  }
1004                  $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1005                  $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1006                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1007                  $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
1008  
1009                  $parsedFrame['encodingid']   = $frame_textencoding;
1010                  $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1011  
1012                  $parsedFrame['language']     = $frame_language;
1013                  $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1014                  if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1015                      $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1016                  }
1017              } else {
1018                  $this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
1019              }
1020              unset($parsedFrame['data']);
1021  
1022  
1023          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
1024                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
1025              //   There may be more than one 'SYLT' frame in each tag,
1026              //   but only one with the same language and content descriptor.
1027              // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1028              // Text encoding        $xx
1029              // Language             $xx xx xx
1030              // Time stamp format    $xx
1031              //   $01  (32-bit value) MPEG frames from beginning of file
1032              //   $02  (32-bit value) milliseconds from beginning of file
1033              // Content type         $xx
1034              // Content descriptor   <text string according to encoding> $00 (00)
1035              //   Terminated text to be synced (typically a syllable)
1036              //   Sync identifier (terminator to above string)   $00 (00)
1037              //   Time stamp                                     $xx (xx ...)
1038  
1039              $frame_offset = 0;
1040              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1041              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1042              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1043                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1044                  $frame_textencoding_terminator = "\x00";
1045              }
1046              $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1047              $frame_offset += 3;
1048              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1049              $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1050              $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1051              $parsedFrame['encodingid']      = $frame_textencoding;
1052              $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
1053  
1054              $parsedFrame['language']        = $frame_language;
1055              $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1056  
1057              $timestampindex = 0;
1058              $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1059              while (strlen($frame_remainingdata)) {
1060                  $frame_offset = 0;
1061                  $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1062                  if ($frame_terminatorpos === false) {
1063                      $frame_remainingdata = '';
1064                  } else {
1065                      if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1066                          $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1067                      }
1068                      $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1069  
1070                      $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
1071                      if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
1072                          // timestamp probably omitted for first data item
1073                      } else {
1074                          $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1075                          $frame_remainingdata = substr($frame_remainingdata, 4);
1076                      }
1077                      $timestampindex++;
1078                  }
1079              }
1080              unset($parsedFrame['data']);
1081  
1082  
1083          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
1084                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
1085              //   There may be more than one comment frame in each tag,
1086              //   but only one with the same language and content descriptor.
1087              // <Header for 'Comment', ID: 'COMM'>
1088              // Text encoding          $xx
1089              // Language               $xx xx xx
1090              // Short content descrip. <text string according to encoding> $00 (00)
1091              // The actual text        <full text string according to encoding>
1092  
1093              if (strlen($parsedFrame['data']) < 5) {
1094  
1095                  $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1096  
1097              } else {
1098  
1099                  $frame_offset = 0;
1100                  $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1101                  $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1102                  if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1103                      $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1104                      $frame_textencoding_terminator = "\x00";
1105                  }
1106                  $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1107                  $frame_offset += 3;
1108                  $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1109                  if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1110                      $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1111                  }
1112                  $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1113                  $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1114                  $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1115                  $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
1116  
1117                  $parsedFrame['encodingid']   = $frame_textencoding;
1118                  $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1119  
1120                  $parsedFrame['language']     = $frame_language;
1121                  $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1122                  $parsedFrame['data']         = $frame_text;
1123                  if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1124                      $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1125                      if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1126                          $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1127                      } else {
1128                          $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1129                      }
1130                  }
1131  
1132              }
1133  
1134          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1135              //   There may be more than one 'RVA2' frame in each tag,
1136              //   but only one with the same identification string
1137              // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1138              // Identification          <text string> $00
1139              //   The 'identification' string is used to identify the situation and/or
1140              //   device where this adjustment should apply. The following is then
1141              //   repeated for every channel:
1142              // Type of channel         $xx
1143              // Volume adjustment       $xx xx
1144              // Bits representing peak  $xx
1145              // Peak volume             $xx (xx ...)
1146  
1147              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1148              $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1149              if (ord($frame_idstring) === 0) {
1150                  $frame_idstring = '';
1151              }
1152              $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1153              $parsedFrame['description'] = $frame_idstring;
1154              $RVA2channelcounter = 0;
1155              while (strlen($frame_remainingdata) >= 5) {
1156                  $frame_offset = 0;
1157                  $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1158                  $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
1159                  $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1160                  $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1161                  $frame_offset += 2;
1162                  $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1163                  if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1164                      $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1165                      break;
1166                  }
1167                  $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1168                  $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1169                  $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1170                  $RVA2channelcounter++;
1171              }
1172              unset($parsedFrame['data']);
1173  
1174  
1175          } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1176                    (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1177              //   There may only be one 'RVA' frame in each tag
1178              // <Header for 'Relative volume adjustment', ID: 'RVA'>
1179              // ID3v2.2 => Increment/decrement     %000000ba
1180              // ID3v2.3 => Increment/decrement     %00fedcba
1181              // Bits used for volume descr.        $xx
1182              // Relative volume change, right      $xx xx (xx ...) // a
1183              // Relative volume change, left       $xx xx (xx ...) // b
1184              // Peak volume right                  $xx xx (xx ...)
1185              // Peak volume left                   $xx xx (xx ...)
1186              //   ID3v2.3 only, optional (not present in ID3v2.2):
1187              // Relative volume change, right back $xx xx (xx ...) // c
1188              // Relative volume change, left back  $xx xx (xx ...) // d
1189              // Peak volume right back             $xx xx (xx ...)
1190              // Peak volume left back              $xx xx (xx ...)
1191              //   ID3v2.3 only, optional (not present in ID3v2.2):
1192              // Relative volume change, center     $xx xx (xx ...) // e
1193              // Peak volume center                 $xx xx (xx ...)
1194              //   ID3v2.3 only, optional (not present in ID3v2.2):
1195              // Relative volume change, bass       $xx xx (xx ...) // f
1196              // Peak volume bass                   $xx xx (xx ...)
1197  
1198              $frame_offset = 0;
1199              $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1200              $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1201              $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
1202              $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1203              $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1204              $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1205              if ($parsedFrame['incdec']['right'] === false) {
1206                  $parsedFrame['volumechange']['right'] *= -1;
1207              }
1208              $frame_offset += $frame_bytesvolume;
1209              $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1210              if ($parsedFrame['incdec']['left'] === false) {
1211                  $parsedFrame['volumechange']['left'] *= -1;
1212              }
1213              $frame_offset += $frame_bytesvolume;
1214              $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1215              $frame_offset += $frame_bytesvolume;
1216              $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1217              $frame_offset += $frame_bytesvolume;
1218              if ($id3v2_majorversion == 3) {
1219                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1220                  if (strlen($parsedFrame['data']) > 0) {
1221                      $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1222                      $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
1223                      $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1224                      if ($parsedFrame['incdec']['rightrear'] === false) {
1225                          $parsedFrame['volumechange']['rightrear'] *= -1;
1226                      }
1227                      $frame_offset += $frame_bytesvolume;
1228                      $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1229                      if ($parsedFrame['incdec']['leftrear'] === false) {
1230                          $parsedFrame['volumechange']['leftrear'] *= -1;
1231                      }
1232                      $frame_offset += $frame_bytesvolume;
1233                      $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1234                      $frame_offset += $frame_bytesvolume;
1235                      $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1236                      $frame_offset += $frame_bytesvolume;
1237                  }
1238                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1239                  if (strlen($parsedFrame['data']) > 0) {
1240                      $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1241                      $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1242                      if ($parsedFrame['incdec']['center'] === false) {
1243                          $parsedFrame['volumechange']['center'] *= -1;
1244                      }
1245                      $frame_offset += $frame_bytesvolume;
1246                      $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1247                      $frame_offset += $frame_bytesvolume;
1248                  }
1249                  $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1250                  if (strlen($parsedFrame['data']) > 0) {
1251                      $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1252                      $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1253                      if ($parsedFrame['incdec']['bass'] === false) {
1254                          $parsedFrame['volumechange']['bass'] *= -1;
1255                      }
1256                      $frame_offset += $frame_bytesvolume;
1257                      $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1258                      $frame_offset += $frame_bytesvolume;
1259                  }
1260              }
1261              unset($parsedFrame['data']);
1262  
1263  
1264          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1265              //   There may be more than one 'EQU2' frame in each tag,
1266              //   but only one with the same identification string
1267              // <Header of 'Equalisation (2)', ID: 'EQU2'>
1268              // Interpolation method  $xx
1269              //   $00  Band
1270              //   $01  Linear
1271              // Identification        <text string> $00
1272              //   The following is then repeated for every adjustment point
1273              // Frequency          $xx xx
1274              // Volume adjustment  $xx xx
1275  
1276              $frame_offset = 0;
1277              $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1278              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1279              $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1280              if (ord($frame_idstring) === 0) {
1281                  $frame_idstring = '';
1282              }
1283              $parsedFrame['description'] = $frame_idstring;
1284              $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1285              while (strlen($frame_remainingdata)) {
1286                  $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1287                  $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1288                  $frame_remainingdata = substr($frame_remainingdata, 4);
1289              }
1290              $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1291              unset($parsedFrame['data']);
1292  
1293  
1294          } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
1295                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1296              //   There may only be one 'EQUA' frame in each tag
1297              // <Header for 'Relative volume adjustment', ID: 'EQU'>
1298              // Adjustment bits    $xx
1299              //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1300              //   nearest byte) for every equalisation band in the following format,
1301              //   giving a frequency range of 0 - 32767Hz:
1302              // Increment/decrement   %x (MSB of the Frequency)
1303              // Frequency             (lower 15 bits)
1304              // Adjustment            $xx (xx ...)
1305  
1306              $frame_offset = 0;
1307              $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1308              $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1309  
1310              $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1311              while (strlen($frame_remainingdata) > 0) {
1312                  $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1313                  $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
1314                  $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1315                  $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1316                  $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1317                  if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1318                      $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1319                  }
1320                  $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1321              }
1322              unset($parsedFrame['data']);
1323  
1324  
1325          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
1326                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1327              //   There may only be one 'RVRB' frame in each tag.
1328              // <Header for 'Reverb', ID: 'RVRB'>
1329              // Reverb left (ms)                 $xx xx
1330              // Reverb right (ms)                $xx xx
1331              // Reverb bounces, left             $xx
1332              // Reverb bounces, right            $xx
1333              // Reverb feedback, left to left    $xx
1334              // Reverb feedback, left to right   $xx
1335              // Reverb feedback, right to right  $xx
1336              // Reverb feedback, right to left   $xx
1337              // Premix left to right             $xx
1338              // Premix right to left             $xx
1339  
1340              $frame_offset = 0;
1341              $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1342              $frame_offset += 2;
1343              $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1344              $frame_offset += 2;
1345              $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1346              $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1347              $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1348              $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1349              $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1350              $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1351              $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1352              $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1353              unset($parsedFrame['data']);
1354  
1355  
1356          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
1357                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1358              //   There may be several pictures attached to one file,
1359              //   each in their individual 'APIC' frame, but only one
1360              //   with the same content descriptor
1361              // <Header for 'Attached picture', ID: 'APIC'>
1362              // Text encoding      $xx
1363              // ID3v2.3+ => MIME type          <text string> $00
1364              // ID3v2.2  => Image format       $xx xx xx
1365              // Picture type       $xx
1366              // Description        <text string according to encoding> $00 (00)
1367              // Picture data       <binary data>
1368  
1369              $frame_offset = 0;
1370              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1371              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1372              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1373                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1374                  $frame_textencoding_terminator = "\x00";
1375              }
1376  
1377              $frame_imagetype = null;
1378              $frame_mimetype = null;
1379              if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1380                  $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1381                  if (strtolower($frame_imagetype) == 'ima') {
1382                      // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1383                      // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1384                      $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1385                      $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1386                      if (ord($frame_mimetype) === 0) {
1387                          $frame_mimetype = '';
1388                      }
1389                      $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1390                      if ($frame_imagetype == 'JPEG') {
1391                          $frame_imagetype = 'JPG';
1392                      }
1393                      $frame_offset = $frame_terminatorpos + strlen("\x00");
1394                  } else {
1395                      $frame_offset += 3;
1396                  }
1397              }
1398              if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1399                  $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1400                  $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1401                  if (ord($frame_mimetype) === 0) {
1402                      $frame_mimetype = '';
1403                  }
1404                  $frame_offset = $frame_terminatorpos + strlen("\x00");
1405              }
1406  
1407              $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1408  
1409              if ($frame_offset >= $parsedFrame['datalength']) {
1410                  $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
1411              } else {
1412                  $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1413                  if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1414                      $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1415                  }
1416                  $parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1417                  $parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1418                  $parsedFrame['encodingid']    = $frame_textencoding;
1419                  $parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);
1420  
1421                  if ($id3v2_majorversion == 2) {
1422                      $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
1423                  } else {
1424                      $parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
1425                  }
1426                  $parsedFrame['picturetypeid'] = $frame_picturetype;
1427                  $parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
1428                  $parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1429                  $parsedFrame['datalength']    = strlen($parsedFrame['data']);
1430  
1431                  $parsedFrame['image_mime']    = '';
1432                  $imageinfo = array();
1433                  if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1434                      if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1435                          $parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
1436                          if ($imagechunkcheck[0]) {
1437                              $parsedFrame['image_width']  = $imagechunkcheck[0];
1438                          }
1439                          if ($imagechunkcheck[1]) {
1440                              $parsedFrame['image_height'] = $imagechunkcheck[1];
1441                          }
1442                      }
1443                  }
1444  
1445                  do {
1446                      if ($this->getid3->option_save_attachments === false) {
1447                          // skip entirely
1448                          unset($parsedFrame['data']);
1449                          break;
1450                      }
1451                      $dir = '';
1452                      if ($this->getid3->option_save_attachments === true) {
1453                          // great
1454  /*
1455                      } elseif (is_int($this->getid3->option_save_attachments)) {
1456                          if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1457                              // too big, skip
1458                              $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1459                              unset($parsedFrame['data']);
1460                              break;
1461                          }
1462  */
1463                      } elseif (is_string($this->getid3->option_save_attachments)) {
1464                          $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1465                          if (!is_dir($dir) || !getID3::is_writable($dir)) {
1466                              // cannot write, skip
1467                              $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1468                              unset($parsedFrame['data']);
1469                              break;
1470                          }
1471                      }
1472                      // if we get this far, must be OK
1473                      if (is_string($this->getid3->option_save_attachments)) {
1474                          $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1475                          if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
1476                              file_put_contents($destination_filename, $parsedFrame['data']);
1477                          } else {
1478                              $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1479                          }
1480                          $parsedFrame['data_filename'] = $destination_filename;
1481                          unset($parsedFrame['data']);
1482                      } else {
1483                          if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1484                              if (!isset($info['id3v2']['comments']['picture'])) {
1485                                  $info['id3v2']['comments']['picture'] = array();
1486                              }
1487                              $comments_picture_data = array();
1488                              foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1489                                  if (isset($parsedFrame[$picture_key])) {
1490                                      $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1491                                  }
1492                              }
1493                              $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1494                              unset($comments_picture_data);
1495                          }
1496                      }
1497                  } while (false);
1498              }
1499  
1500          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
1501                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1502              //   There may be more than one 'GEOB' frame in each tag,
1503              //   but only one with the same content descriptor
1504              // <Header for 'General encapsulated object', ID: 'GEOB'>
1505              // Text encoding          $xx
1506              // MIME type              <text string> $00
1507              // Filename               <text string according to encoding> $00 (00)
1508              // Content description    <text string according to encoding> $00 (00)
1509              // Encapsulated object    <binary data>
1510  
1511              $frame_offset = 0;
1512              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1513              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1514              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1515                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1516                  $frame_textencoding_terminator = "\x00";
1517              }
1518              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1519              $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1520              if (ord($frame_mimetype) === 0) {
1521                  $frame_mimetype = '';
1522              }
1523              $frame_offset = $frame_terminatorpos + strlen("\x00");
1524  
1525              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1526              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1527                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1528              }
1529              $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1530              if (ord($frame_filename) === 0) {
1531                  $frame_filename = '';
1532              }
1533              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1534  
1535              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1536              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1537                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1538              }
1539              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1540              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1541              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1542  
1543              $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1544              $parsedFrame['encodingid']  = $frame_textencoding;
1545              $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1546  
1547              $parsedFrame['mime']        = $frame_mimetype;
1548              $parsedFrame['filename']    = $frame_filename;
1549              unset($parsedFrame['data']);
1550  
1551  
1552          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
1553                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
1554              //   There may only be one 'PCNT' frame in each tag.
1555              //   When the counter reaches all one's, one byte is inserted in
1556              //   front of the counter thus making the counter eight bits bigger
1557              // <Header for 'Play counter', ID: 'PCNT'>
1558              // Counter        $xx xx xx xx (xx ...)
1559  
1560              $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1561  
1562  
1563          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
1564                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
1565              //   There may be more than one 'POPM' frame in each tag,
1566              //   but only one with the same email address
1567              // <Header for 'Popularimeter', ID: 'POPM'>
1568              // Email to user   <text string> $00
1569              // Rating          $xx
1570              // Counter         $xx xx xx xx (xx ...)
1571  
1572              $frame_offset = 0;
1573              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1574              $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1575              if (ord($frame_emailaddress) === 0) {
1576                  $frame_emailaddress = '';
1577              }
1578              $frame_offset = $frame_terminatorpos + strlen("\x00");
1579              $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1580              $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1581              $parsedFrame['email']   = $frame_emailaddress;
1582              $parsedFrame['rating']  = $frame_rating;
1583              unset($parsedFrame['data']);
1584  
1585  
1586          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
1587                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
1588              //   There may only be one 'RBUF' frame in each tag
1589              // <Header for 'Recommended buffer size', ID: 'RBUF'>
1590              // Buffer size               $xx xx xx
1591              // Embedded info flag        %0000000x
1592              // Offset to next tag        $xx xx xx xx
1593  
1594              $frame_offset = 0;
1595              $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1596              $frame_offset += 3;
1597  
1598              $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1599              $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1600              $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1601              unset($parsedFrame['data']);
1602  
1603  
1604          } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1605              //   There may be more than one 'CRM' frame in a tag,
1606              //   but only one with the same 'owner identifier'
1607              // <Header for 'Encrypted meta frame', ID: 'CRM'>
1608              // Owner identifier      <textstring> $00 (00)
1609              // Content/explanation   <textstring> $00 (00)
1610              // Encrypted datablock   <binary data>
1611  
1612              $frame_offset = 0;
1613              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1614              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1615              $frame_offset = $frame_terminatorpos + strlen("\x00");
1616  
1617              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1618              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1619              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1620              $frame_offset = $frame_terminatorpos + strlen("\x00");
1621  
1622              $parsedFrame['ownerid']     = $frame_ownerid;
1623              $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1624              unset($parsedFrame['data']);
1625  
1626  
1627          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
1628                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
1629              //   There may be more than one 'AENC' frames in a tag,
1630              //   but only one with the same 'Owner identifier'
1631              // <Header for 'Audio encryption', ID: 'AENC'>
1632              // Owner identifier   <text string> $00
1633              // Preview start      $xx xx
1634              // Preview length     $xx xx
1635              // Encryption info    <binary data>
1636  
1637              $frame_offset = 0;
1638              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1639              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1640              if (ord($frame_ownerid) === 0) {
1641                  $frame_ownerid = '';
1642              }
1643              $frame_offset = $frame_terminatorpos + strlen("\x00");
1644              $parsedFrame['ownerid'] = $frame_ownerid;
1645              $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1646              $frame_offset += 2;
1647              $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1648              $frame_offset += 2;
1649              $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1650              unset($parsedFrame['data']);
1651  
1652  
1653          } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
1654                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
1655              //   There may be more than one 'LINK' frame in a tag,
1656              //   but only one with the same contents
1657              // <Header for 'Linked information', ID: 'LINK'>
1658              // ID3v2.3+ => Frame identifier   $xx xx xx xx
1659              // ID3v2.2  => Frame identifier   $xx xx xx
1660              // URL                            <text string> $00
1661              // ID and additional data         <text string(s)>
1662  
1663              $frame_offset = 0;
1664              if ($id3v2_majorversion == 2) {
1665                  $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1666                  $frame_offset += 3;
1667              } else {
1668                  $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1669                  $frame_offset += 4;
1670              }
1671  
1672              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1673              $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1674              if (ord($frame_url) === 0) {
1675                  $frame_url = '';
1676              }
1677              $frame_offset = $frame_terminatorpos + strlen("\x00");
1678              $parsedFrame['url'] = $frame_url;
1679  
1680              $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1681              if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1682                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1683              }
1684              unset($parsedFrame['data']);
1685  
1686  
1687          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1688              //   There may only be one 'POSS' frame in each tag
1689              // <Head for 'Position synchronisation', ID: 'POSS'>
1690              // Time stamp format         $xx
1691              // Position                  $xx (xx ...)
1692  
1693              $frame_offset = 0;
1694              $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1695              $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1696              unset($parsedFrame['data']);
1697  
1698  
1699          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1700              //   There may be more than one 'Terms of use' frame in a tag,
1701              //   but only one with the same 'Language'
1702              // <Header for 'Terms of use frame', ID: 'USER'>
1703              // Text encoding        $xx
1704              // Language             $xx xx xx
1705              // The actual text      <text string according to encoding>
1706  
1707              $frame_offset = 0;
1708              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1709              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1710                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1711              }
1712              $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1713              $frame_offset += 3;
1714              $parsedFrame['language']     = $frame_language;
1715              $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1716              $parsedFrame['encodingid']   = $frame_textencoding;
1717              $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1718  
1719              $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1720              $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1721              if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1722                  $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1723              }
1724              unset($parsedFrame['data']);
1725  
1726  
1727          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1728              //   There may only be one 'OWNE' frame in a tag
1729              // <Header for 'Ownership frame', ID: 'OWNE'>
1730              // Text encoding     $xx
1731              // Price paid        <text string> $00
1732              // Date of purch.    <text string>
1733              // Seller            <text string according to encoding>
1734  
1735              $frame_offset = 0;
1736              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1737              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1738                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1739              }
1740              $parsedFrame['encodingid'] = $frame_textencoding;
1741              $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1742  
1743              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1744              $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1745              $frame_offset = $frame_terminatorpos + strlen("\x00");
1746  
1747              $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1748              $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1749              $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1750  
1751              $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1752              if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1753                  $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1754              }
1755              $frame_offset += 8;
1756  
1757              $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1758              $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1759              unset($parsedFrame['data']);
1760  
1761  
1762          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1763              //   There may be more than one 'commercial frame' in a tag,
1764              //   but no two may be identical
1765              // <Header for 'Commercial frame', ID: 'COMR'>
1766              // Text encoding      $xx
1767              // Price string       <text string> $00
1768              // Valid until        <text string>
1769              // Contact URL        <text string> $00
1770              // Received as        $xx
1771              // Name of seller     <text string according to encoding> $00 (00)
1772              // Description        <text string according to encoding> $00 (00)
1773              // Picture MIME type  <string> $00
1774              // Seller logo        <binary data>
1775  
1776              $frame_offset = 0;
1777              $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1778              $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1779              if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1780                  $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1781                  $frame_textencoding_terminator = "\x00";
1782              }
1783  
1784              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1785              $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1786              $frame_offset = $frame_terminatorpos + strlen("\x00");
1787              $frame_rawpricearray = explode('/', $frame_pricestring);
1788              foreach ($frame_rawpricearray as $key => $val) {
1789                  $frame_currencyid = substr($val, 0, 3);
1790                  $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1791                  $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
1792              }
1793  
1794              $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1795              $frame_offset += 8;
1796  
1797              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1798              $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1799              $frame_offset = $frame_terminatorpos + strlen("\x00");
1800  
1801              $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1802  
1803              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1804              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1805                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1806              }
1807              $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1808              if (ord($frame_sellername) === 0) {
1809                  $frame_sellername = '';
1810              }
1811              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1812  
1813              $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1814              if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1815                  $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1816              }
1817              $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1818              $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1819              $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1820  
1821              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1822              $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1823              $frame_offset = $frame_terminatorpos + strlen("\x00");
1824  
1825              $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1826  
1827              $parsedFrame['encodingid']        = $frame_textencoding;
1828              $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1829  
1830              $parsedFrame['pricevaliduntil']   = $frame_datestring;
1831              $parsedFrame['contacturl']        = $frame_contacturl;
1832              $parsedFrame['receivedasid']      = $frame_receivedasid;
1833              $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
1834              $parsedFrame['sellername']        = $frame_sellername;
1835              $parsedFrame['mime']              = $frame_mimetype;
1836              $parsedFrame['logo']              = $frame_sellerlogo;
1837              unset($parsedFrame['data']);
1838  
1839  
1840          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1841              //   There may be several 'ENCR' frames in a tag,
1842              //   but only one containing the same symbol
1843              //   and only one containing the same owner identifier
1844              // <Header for 'Encryption method registration', ID: 'ENCR'>
1845              // Owner identifier    <text string> $00
1846              // Method symbol       $xx
1847              // Encryption data     <binary data>
1848  
1849              $frame_offset = 0;
1850              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1851              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1852              if (ord($frame_ownerid) === 0) {
1853                  $frame_ownerid = '';
1854              }
1855              $frame_offset = $frame_terminatorpos + strlen("\x00");
1856  
1857              $parsedFrame['ownerid']      = $frame_ownerid;
1858              $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1859              $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1860  
1861  
1862          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1863  
1864              //   There may be several 'GRID' frames in a tag,
1865              //   but only one containing the same symbol
1866              //   and only one containing the same owner identifier
1867              // <Header for 'Group ID registration', ID: 'GRID'>
1868              // Owner identifier      <text string> $00
1869              // Group symbol          $xx
1870              // Group dependent data  <binary data>
1871  
1872              $frame_offset = 0;
1873              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1874              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1875              if (ord($frame_ownerid) === 0) {
1876                  $frame_ownerid = '';
1877              }
1878              $frame_offset = $frame_terminatorpos + strlen("\x00");
1879  
1880              $parsedFrame['ownerid']       = $frame_ownerid;
1881              $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1882              $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1883  
1884  
1885          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1886              //   The tag may contain more than one 'PRIV' frame
1887              //   but only with different contents
1888              // <Header for 'Private frame', ID: 'PRIV'>
1889              // Owner identifier      <text string> $00
1890              // The private data      <binary data>
1891  
1892              $frame_offset = 0;
1893              $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1894              $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1895              if (ord($frame_ownerid) === 0) {
1896                  $frame_ownerid = '';
1897              }
1898              $frame_offset = $frame_terminatorpos + strlen("\x00");
1899  
1900              $parsedFrame['ownerid'] = $frame_ownerid;
1901              $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1902  
1903  
1904          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1905              //   There may be more than one 'signature frame' in a tag,
1906              //   but no two may be identical
1907              // <Header for 'Signature frame', ID: 'SIGN'>
1908              // Group symbol      $xx
1909              // Signature         <binary data>
1910  
1911              $frame_offset = 0;
1912              $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1913              $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1914  
1915  
1916          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1917              //   There may only be one 'seek frame' in a tag
1918              // <Header for 'Seek frame', ID: 'SEEK'>
1919              // Minimum offset to next tag       $xx xx xx xx
1920  
1921              $frame_offset = 0;
1922              $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1923  
1924  
1925          } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1926              //   There may only be one 'audio seek point index' frame in a tag
1927              // <Header for 'Seek Point Index', ID: 'ASPI'>
1928              // Indexed data start (S)         $xx xx xx xx
1929              // Indexed data length (L)        $xx xx xx xx
1930              // Number of index points (N)     $xx xx
1931              // Bits per index point (b)       $xx
1932              //   Then for every index point the following data is included:
1933              // Fraction at index (Fi)          $xx (xx)
1934  
1935              $frame_offset = 0;
1936              $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1937              $frame_offset += 4;
1938              $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1939              $frame_offset += 4;
1940              $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1941              $frame_offset += 2;
1942              $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1943              $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1944              for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1945                  $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1946                  $frame_offset += $frame_bytesperpoint;
1947              }
1948              unset($parsedFrame['data']);
1949  
1950          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1951              // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1952              //   There may only be one 'RGAD' frame in a tag
1953              // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1954              // Peak Amplitude                      $xx $xx $xx $xx
1955              // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1956              // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1957              //   a - name code
1958              //   b - originator code
1959              //   c - sign bit
1960              //   d - replay gain adjustment
1961  
1962              $frame_offset = 0;
1963              $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1964              $frame_offset += 4;
1965              foreach (array('track','album') as $rgad_entry_type) {
1966                  $rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1967                  $frame_offset += 2;
1968                  $parsedFrame['raw'][$rgad_entry_type]['name']       = ($rg_adjustment_word & 0xE000) >> 13;
1969                  $parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
1970                  $parsedFrame['raw'][$rgad_entry_type]['signbit']    = ($rg_adjustment_word & 0x0200) >>  9;
1971                  $parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
1972              }
1973              $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1974              $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1975              $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1976              $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1977              $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1978              $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1979  
1980              $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
1981              $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1982              $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1983              $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1984              $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1985  
1986              unset($parsedFrame['data']);
1987  
1988          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
1989              // http://id3.org/id3v2-chapters-1.0
1990              // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
1991              // Element ID      <text string> $00
1992              // Start time      $xx xx xx xx
1993              // End time        $xx xx xx xx
1994              // Start offset    $xx xx xx xx
1995              // End offset      $xx xx xx xx
1996              // <Optional embedded sub-frames>
1997  
1998              $frame_offset = 0;
1999              @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2000              $frame_offset += strlen($parsedFrame['element_id']."\x00");
2001              $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2002              $frame_offset += 4;
2003              $parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2004              $frame_offset += 4;
2005              if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2006                  // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2007                  $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2008              }
2009              $frame_offset += 4;
2010              if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2011                  // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2012                  $parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2013              }
2014              $frame_offset += 4;
2015  
2016              if ($frame_offset < strlen($parsedFrame['data'])) {
2017                  $parsedFrame['subframes'] = array();
2018                  while ($frame_offset < strlen($parsedFrame['data'])) {
2019                      // <Optional embedded sub-frames>
2020                      $subframe = array();
2021                      $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2022                      $frame_offset += 4;
2023                      $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2024                      $frame_offset += 4;
2025                      $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2026                      $frame_offset += 2;
2027                      if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2028                          $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)');
2029                          break;
2030                      }
2031                      $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2032                      $frame_offset += $subframe['size'];
2033  
2034                      $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2035                      $subframe['text']       =     substr($subframe_rawdata, 1);
2036                      $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2037                      $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
2038                      switch (substr($encoding_converted_text, 0, 2)) {
2039                          case "\xFF\xFE":
2040                          case "\xFE\xFF":
2041                              switch (strtoupper($info['id3v2']['encoding'])) {
2042                                  case 'ISO-8859-1':
2043                                  case 'UTF-8':
2044                                      $encoding_converted_text = substr($encoding_converted_text, 2);
2045                                      // remove unwanted byte-order-marks
2046                                      break;
2047                                  default:
2048                                      // ignore
2049                                      break;
2050                              }
2051                              break;
2052                          default:
2053                              // do not remove BOM
2054                              break;
2055                      }
2056  
2057                      switch ($subframe['name']) {
2058                          case 'TIT2':
2059                              $parsedFrame['chapter_name']        = $encoding_converted_text;
2060                              $parsedFrame['subframes'][] = $subframe;
2061                              break;
2062                          case 'TIT3':
2063                              $parsedFrame['chapter_description'] = $encoding_converted_text;
2064                              $parsedFrame['subframes'][] = $subframe;
2065                              break;
2066                          case 'WXXX':
2067                              list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
2068                              $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
2069                              $parsedFrame['subframes'][] = $subframe;
2070                              break;
2071                          case 'APIC':
2072                              if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
2073                                  list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
2074                                  $subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
2075                                  $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
2076                                  $subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
2077                                  if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
2078                                      // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
2079                                      // the above regex assumes one byte, if it's actually two then strip the second one here
2080                                      $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
2081                                  }
2082                                  $subframe['data'] = $subframe_apic_picturedata;
2083                                  unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
2084                                  unset($subframe['text'], $parsedFrame['text']);
2085                                  $parsedFrame['subframes'][] = $subframe;
2086                                  $parsedFrame['picture_present'] = true;
2087                              } else {
2088                                  $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
2089                              }
2090                              break;
2091                          default:
2092                              $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
2093                              break;
2094                      }
2095                  }
2096                  unset($subframe_rawdata, $subframe, $encoding_converted_text);
2097                  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
2098              }
2099  
2100              $id3v2_chapter_entry = array();
2101              foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
2102                  if (isset($parsedFrame[$id3v2_chapter_key])) {
2103                      $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2104                  }
2105              }
2106              if (!isset($info['id3v2']['chapters'])) {
2107                  $info['id3v2']['chapters'] = array();
2108              }
2109              $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2110              unset($id3v2_chapter_entry, $id3v2_chapter_key);
2111  
2112  
2113          } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2114              // http://id3.org/id3v2-chapters-1.0
2115              // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
2116              // Element ID      <text string> $00
2117              // CTOC flags        %xx
2118              // Entry count       $xx
2119              // Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
2120              // <Optional embedded sub-frames>
2121  
2122              $frame_offset = 0;
2123              @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2124              $frame_offset += strlen($parsedFrame['element_id']."\x00");
2125              $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2126              $frame_offset += 1;
2127              $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2128              $frame_offset += 1;
2129  
2130              $terminator_position = null;
2131              for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
2132                  $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2133                  $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2134                  $frame_offset = $terminator_position + 1;
2135              }
2136  
2137              $parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
2138              $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2139  
2140              unset($ctoc_flags_raw, $terminator_position);
2141  
2142              if ($frame_offset < strlen($parsedFrame['data'])) {
2143                  $parsedFrame['subframes'] = array();
2144                  while ($frame_offset < strlen($parsedFrame['data'])) {
2145                      // <Optional embedded sub-frames>
2146                      $subframe = array();
2147                      $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2148                      $frame_offset += 4;
2149                      $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2150                      $frame_offset += 4;
2151                      $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2152                      $frame_offset += 2;
2153                      if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2154                          $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)');
2155                          break;
2156                      }
2157                      $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2158                      $frame_offset += $subframe['size'];
2159  
2160                      $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2161                      $subframe['text']       =     substr($subframe_rawdata, 1);
2162                      $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2163                      $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2164                      switch (substr($encoding_converted_text, 0, 2)) {
2165                          case "\xFF\xFE":
2166                          case "\xFE\xFF":
2167                              switch (strtoupper($info['id3v2']['encoding'])) {
2168                                  case 'ISO-8859-1':
2169                                  case 'UTF-8':
2170                                      $encoding_converted_text = substr($encoding_converted_text, 2);
2171                                      // remove unwanted byte-order-marks
2172                                      break;
2173                                  default:
2174                                      // ignore
2175                                      break;
2176                              }
2177                              break;
2178                          default:
2179                              // do not remove BOM
2180                              break;
2181                      }
2182  
2183                      if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2184                          if ($subframe['name'] == 'TIT2') {
2185                              $parsedFrame['toc_name']        = $encoding_converted_text;
2186                          } elseif ($subframe['name'] == 'TIT3') {
2187                              $parsedFrame['toc_description'] = $encoding_converted_text;
2188                          }
2189                          $parsedFrame['subframes'][] = $subframe;
2190                      } else {
2191                          $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2192                      }
2193                  }
2194                  unset($subframe_rawdata, $subframe, $encoding_converted_text);
2195              }
2196  
2197          }
2198  
2199          return true;
2200      }
2201  
2202      /**
2203       * @param string $data
2204       *
2205       * @return string
2206       */
2207  	public function DeUnsynchronise($data) {
2208          return str_replace("\xFF\x00", "\xFF", $data);
2209      }
2210  
2211      /**
2212       * @param int $index
2213       *
2214       * @return string
2215       */
2216  	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2217          static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2218              0x00 => 'No more than 128 frames and 1 MB total tag size',
2219              0x01 => 'No more than 64 frames and 128 KB total tag size',
2220              0x02 => 'No more than 32 frames and 40 KB total tag size',
2221              0x03 => 'No more than 32 frames and 4 KB total tag size',
2222          );
2223          return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2224      }
2225  
2226      /**
2227       * @param int $index
2228       *
2229       * @return string
2230       */
2231  	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2232          static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2233              0x00 => 'No restrictions',
2234              0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2235          );
2236          return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2237      }
2238  
2239      /**
2240       * @param int $index
2241       *
2242       * @return string
2243       */
2244  	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2245          static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2246              0x00 => 'No restrictions',
2247              0x01 => 'No string is longer than 1024 characters',
2248              0x02 => 'No string is longer than 128 characters',
2249              0x03 => 'No string is longer than 30 characters',
2250          );
2251          return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2252      }
2253  
2254      /**
2255       * @param int $index
2256       *
2257       * @return string
2258       */
2259  	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2260          static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2261              0x00 => 'No restrictions',
2262              0x01 => 'Images are encoded only with PNG or JPEG',
2263          );
2264          return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2265      }
2266  
2267      /**
2268       * @param int $index
2269       *
2270       * @return string
2271       */
2272  	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2273          static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2274              0x00 => 'No restrictions',
2275              0x01 => 'All images are 256x256 pixels or smaller',
2276              0x02 => 'All images are 64x64 pixels or smaller',
2277              0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2278          );
2279          return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2280      }
2281  
2282      /**
2283       * @param string $currencyid
2284       *
2285       * @return string
2286       */
2287  	public function LookupCurrencyUnits($currencyid) {
2288  
2289          $begin = __LINE__;
2290  
2291          /** This is not a comment!
2292  
2293  
2294              AED    Dirhams
2295              AFA    Afghanis
2296              ALL    Leke
2297              AMD    Drams
2298              ANG    Guilders
2299              AOA    Kwanza
2300              ARS    Pesos
2301              ATS    Schillings
2302              AUD    Dollars
2303              AWG    Guilders
2304              AZM    Manats
2305              BAM    Convertible Marka
2306              BBD    Dollars
2307              BDT    Taka
2308              BEF    Francs
2309              BGL    Leva
2310              BHD    Dinars
2311              BIF    Francs
2312              BMD    Dollars
2313              BND    Dollars
2314              BOB    Bolivianos
2315              BRL    Brazil Real
2316              BSD    Dollars
2317              BTN    Ngultrum
2318              BWP    Pulas
2319              BYR    Rubles
2320              BZD    Dollars
2321              CAD    Dollars
2322              CDF    Congolese Francs
2323              CHF    Francs
2324              CLP    Pesos
2325              CNY    Yuan Renminbi
2326              COP    Pesos
2327              CRC    Colones
2328              CUP    Pesos
2329              CVE    Escudos
2330              CYP    Pounds
2331              CZK    Koruny
2332              DEM    Deutsche Marks
2333              DJF    Francs
2334              DKK    Kroner
2335              DOP    Pesos
2336              DZD    Algeria Dinars
2337              EEK    Krooni
2338              EGP    Pounds
2339              ERN    Nakfa
2340              ESP    Pesetas
2341              ETB    Birr
2342              EUR    Euro
2343              FIM    Markkaa
2344              FJD    Dollars
2345              FKP    Pounds
2346              FRF    Francs
2347              GBP    Pounds
2348              GEL    Lari
2349              GGP    Pounds
2350              GHC    Cedis
2351              GIP    Pounds
2352              GMD    Dalasi
2353              GNF    Francs
2354              GRD    Drachmae
2355              GTQ    Quetzales
2356              GYD    Dollars
2357              HKD    Dollars
2358              HNL    Lempiras
2359              HRK    Kuna
2360              HTG    Gourdes
2361              HUF    Forints
2362              IDR    Rupiahs
2363              IEP    Pounds
2364              ILS    New Shekels
2365              IMP    Pounds
2366              INR    Rupees
2367              IQD    Dinars
2368              IRR    Rials
2369              ISK    Kronur
2370              ITL    Lire
2371              JEP    Pounds
2372              JMD    Dollars
2373              JOD    Dinars
2374              JPY    Yen
2375              KES    Shillings
2376              KGS    Soms
2377              KHR    Riels
2378              KMF    Francs
2379              KPW    Won
2380              KWD    Dinars
2381              KYD    Dollars
2382              KZT    Tenge
2383              LAK    Kips
2384              LBP    Pounds
2385              LKR    Rupees
2386              LRD    Dollars
2387              LSL    Maloti
2388              LTL    Litai
2389              LUF    Francs
2390              LVL    Lati
2391              LYD    Dinars
2392              MAD    Dirhams
2393              MDL    Lei
2394              MGF    Malagasy Francs
2395              MKD    Denars
2396              MMK    Kyats
2397              MNT    Tugriks
2398              MOP    Patacas
2399              MRO    Ouguiyas
2400              MTL    Liri
2401              MUR    Rupees
2402              MVR    Rufiyaa
2403              MWK    Kwachas
2404              MXN    Pesos
2405              MYR    Ringgits
2406              MZM    Meticais
2407              NAD    Dollars
2408              NGN    Nairas
2409              NIO    Gold Cordobas
2410              NLG    Guilders
2411              NOK    Krone
2412              NPR    Nepal Rupees
2413              NZD    Dollars
2414              OMR    Rials
2415              PAB    Balboa
2416              PEN    Nuevos Soles
2417              PGK    Kina
2418              PHP    Pesos
2419              PKR    Rupees
2420              PLN    Zlotych
2421              PTE    Escudos
2422              PYG    Guarani
2423              QAR    Rials
2424              ROL    Lei
2425              RUR    Rubles
2426              RWF    Rwanda Francs
2427              SAR    Riyals
2428              SBD    Dollars
2429              SCR    Rupees
2430              SDD    Dinars
2431              SEK    Kronor
2432              SGD    Dollars
2433              SHP    Pounds
2434              SIT    Tolars
2435              SKK    Koruny
2436              SLL    Leones
2437              SOS    Shillings
2438              SPL    Luigini
2439              SRG    Guilders
2440              STD    Dobras
2441              SVC    Colones
2442              SYP    Pounds
2443              SZL    Emalangeni
2444              THB    Baht
2445              TJR    Rubles
2446              TMM    Manats
2447              TND    Dinars
2448              TOP    Pa'anga
2449              TRL    Liras (old)
2450              TRY    Liras
2451              TTD    Dollars
2452              TVD    Tuvalu Dollars
2453              TWD    New Dollars
2454              TZS    Shillings
2455              UAH    Hryvnia
2456              UGX    Shillings
2457              USD    Dollars
2458              UYU    Pesos
2459              UZS    Sums
2460              VAL    Lire
2461              VEB    Bolivares
2462              VND    Dong
2463              VUV    Vatu
2464              WST    Tala
2465              XAF    Francs
2466              XAG    Ounces
2467              XAU    Ounces
2468              XCD    Dollars
2469              XDR    Special Drawing Rights
2470              XPD    Ounces
2471              XPF    Francs
2472              XPT    Ounces
2473              YER    Rials
2474              YUM    New Dinars
2475              ZAR    Rand
2476              ZMK    Kwacha
2477              ZWD    Zimbabwe Dollars
2478  
2479          */
2480  
2481          return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2482      }
2483  
2484      /**
2485       * @param string $currencyid
2486       *
2487       * @return string
2488       */
2489  	public function LookupCurrencyCountry($currencyid) {
2490  
2491          $begin = __LINE__;
2492  
2493          /** This is not a comment!
2494  
2495              AED    United Arab Emirates
2496              AFA    Afghanistan
2497              ALL    Albania
2498              AMD    Armenia
2499              ANG    Netherlands Antilles
2500              AOA    Angola
2501              ARS    Argentina
2502              ATS    Austria
2503              AUD    Australia
2504              AWG    Aruba
2505              AZM    Azerbaijan
2506              BAM    Bosnia and Herzegovina
2507              BBD    Barbados
2508              BDT    Bangladesh
2509              BEF    Belgium
2510              BGL    Bulgaria
2511              BHD    Bahrain
2512              BIF    Burundi
2513              BMD    Bermuda
2514              BND    Brunei Darussalam
2515              BOB    Bolivia
2516              BRL    Brazil
2517              BSD    Bahamas
2518              BTN    Bhutan
2519              BWP    Botswana
2520              BYR    Belarus
2521              BZD    Belize
2522              CAD    Canada
2523              CDF    Congo/Kinshasa
2524              CHF    Switzerland
2525              CLP    Chile
2526              CNY    China
2527              COP    Colombia
2528              CRC    Costa Rica
2529              CUP    Cuba
2530              CVE    Cape Verde
2531              CYP    Cyprus
2532              CZK    Czech Republic
2533              DEM    Germany
2534              DJF    Djibouti
2535              DKK    Denmark
2536              DOP    Dominican Republic
2537              DZD    Algeria
2538              EEK    Estonia
2539              EGP    Egypt
2540              ERN    Eritrea
2541              ESP    Spain
2542              ETB    Ethiopia
2543              EUR    Euro Member Countries
2544              FIM    Finland
2545              FJD    Fiji
2546              FKP    Falkland Islands (Malvinas)
2547              FRF    France
2548              GBP    United Kingdom
2549              GEL    Georgia
2550              GGP    Guernsey
2551              GHC    Ghana
2552              GIP    Gibraltar
2553              GMD    Gambia
2554              GNF    Guinea
2555              GRD    Greece
2556              GTQ    Guatemala
2557              GYD    Guyana
2558              HKD    Hong Kong
2559              HNL    Honduras
2560              HRK    Croatia
2561              HTG    Haiti
2562              HUF    Hungary
2563              IDR    Indonesia
2564              IEP    Ireland (Eire)
2565              ILS    Israel
2566              IMP    Isle of Man
2567              INR    India
2568              IQD    Iraq
2569              IRR    Iran
2570              ISK    Iceland
2571              ITL    Italy
2572              JEP    Jersey
2573              JMD    Jamaica
2574              JOD    Jordan
2575              JPY    Japan
2576              KES    Kenya
2577              KGS    Kyrgyzstan
2578              KHR    Cambodia
2579              KMF    Comoros
2580              KPW    Korea
2581              KWD    Kuwait
2582              KYD    Cayman Islands
2583              KZT    Kazakstan
2584              LAK    Laos
2585              LBP    Lebanon
2586              LKR    Sri Lanka
2587              LRD    Liberia
2588              LSL    Lesotho
2589              LTL    Lithuania
2590              LUF    Luxembourg
2591              LVL    Latvia
2592              LYD    Libya
2593              MAD    Morocco
2594              MDL    Moldova
2595              MGF    Madagascar
2596              MKD    Macedonia
2597              MMK    Myanmar (Burma)
2598              MNT    Mongolia
2599              MOP    Macau
2600              MRO    Mauritania
2601              MTL    Malta
2602              MUR    Mauritius
2603              MVR    Maldives (Maldive Islands)
2604              MWK    Malawi
2605              MXN    Mexico
2606              MYR    Malaysia
2607              MZM    Mozambique
2608              NAD    Namibia
2609              NGN    Nigeria
2610              NIO    Nicaragua
2611              NLG    Netherlands (Holland)
2612              NOK    Norway
2613              NPR    Nepal
2614              NZD    New Zealand
2615              OMR    Oman
2616              PAB    Panama
2617              PEN    Peru
2618              PGK    Papua New Guinea
2619              PHP    Philippines
2620              PKR    Pakistan
2621              PLN    Poland
2622              PTE    Portugal
2623              PYG    Paraguay
2624              QAR    Qatar
2625              ROL    Romania
2626              RUR    Russia
2627              RWF    Rwanda
2628              SAR    Saudi Arabia
2629              SBD    Solomon Islands
2630              SCR    Seychelles
2631              SDD    Sudan
2632              SEK    Sweden
2633              SGD    Singapore
2634              SHP    Saint Helena
2635              SIT    Slovenia
2636              SKK    Slovakia
2637              SLL    Sierra Leone
2638              SOS    Somalia
2639              SPL    Seborga
2640              SRG    Suriname
2641              STD    São Tome and Principe
2642              SVC    El Salvador
2643              SYP    Syria
2644              SZL    Swaziland
2645              THB    Thailand
2646              TJR    Tajikistan
2647              TMM    Turkmenistan
2648              TND    Tunisia
2649              TOP    Tonga
2650              TRL    Turkey
2651              TRY    Turkey
2652              TTD    Trinidad and Tobago
2653              TVD    Tuvalu
2654              TWD    Taiwan
2655              TZS    Tanzania
2656              UAH    Ukraine
2657              UGX    Uganda
2658              USD    United States of America
2659              UYU    Uruguay
2660              UZS    Uzbekistan
2661              VAL    Vatican City
2662              VEB    Venezuela
2663              VND    Viet Nam
2664              VUV    Vanuatu
2665              WST    Samoa
2666              XAF    Communauté Financière Africaine
2667              XAG    Silver
2668              XAU    Gold
2669              XCD    East Caribbean
2670              XDR    International Monetary Fund
2671              XPD    Palladium
2672              XPF    Comptoirs Français du Pacifique
2673              XPT    Platinum
2674              YER    Yemen
2675              YUM    Yugoslavia
2676              ZAR    South Africa
2677              ZMK    Zambia
2678              ZWD    Zimbabwe
2679  
2680          */
2681  
2682          return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2683      }
2684  
2685      /**
2686       * @param string $languagecode
2687       * @param bool   $casesensitive
2688       *
2689       * @return string
2690       */
2691  	public static function LanguageLookup($languagecode, $casesensitive=false) {
2692  
2693          if (!$casesensitive) {
2694              $languagecode = strtolower($languagecode);
2695          }
2696  
2697          // http://www.id3.org/id3v2.4.0-structure.txt
2698          // [4.   ID3v2 frame overview]
2699          // The three byte language field, present in several frames, is used to
2700          // describe the language of the frame's content, according to ISO-639-2
2701          // [ISO-639-2]. The language should be represented in lower case. If the
2702          // language is not known the string "XXX" should be used.
2703  
2704  
2705          // ISO 639-2 - http://www.id3.org/iso639-2.html
2706  
2707          $begin = __LINE__;
2708  
2709          /** This is not a comment!
2710  
2711              XXX    unknown
2712              xxx    unknown
2713              aar    Afar
2714              abk    Abkhazian
2715              ace    Achinese
2716              ach    Acoli
2717              ada    Adangme
2718              afa    Afro-Asiatic (Other)
2719              afh    Afrihili
2720              afr    Afrikaans
2721              aka    Akan
2722              akk    Akkadian
2723              alb    Albanian
2724              ale    Aleut
2725              alg    Algonquian Languages
2726              amh    Amharic
2727              ang    English, Old (ca. 450-1100)
2728              apa    Apache Languages
2729              ara    Arabic
2730              arc    Aramaic
2731              arm    Armenian
2732              arn    Araucanian
2733              arp    Arapaho
2734              art    Artificial (Other)
2735              arw    Arawak
2736              asm    Assamese
2737              ath    Athapascan Languages
2738              ava    Avaric
2739              ave    Avestan
2740              awa    Awadhi
2741              aym    Aymara
2742              aze    Azerbaijani
2743              bad    Banda
2744              bai    Bamileke Languages
2745              bak    Bashkir
2746              bal    Baluchi
2747              bam    Bambara
2748              ban    Balinese
2749              baq    Basque
2750              bas    Basa
2751              bat    Baltic (Other)
2752              bej    Beja
2753              bel    Byelorussian
2754              bem    Bemba
2755              ben    Bengali
2756              ber    Berber (Other)
2757              bho    Bhojpuri
2758              bih    Bihari
2759              bik    Bikol
2760              bin    Bini
2761              bis    Bislama
2762              bla    Siksika
2763              bnt    Bantu (Other)
2764              bod    Tibetan
2765              bra    Braj
2766              bre    Breton
2767              bua    Buriat
2768              bug    Buginese
2769              bul    Bulgarian
2770              bur    Burmese
2771              cad    Caddo
2772              cai    Central American Indian (Other)
2773              car    Carib
2774              cat    Catalan
2775              cau    Caucasian (Other)
2776              ceb    Cebuano
2777              cel    Celtic (Other)
2778              ces    Czech
2779              cha    Chamorro
2780              chb    Chibcha
2781              che    Chechen
2782              chg    Chagatai
2783              chi    Chinese
2784              chm    Mari
2785              chn    Chinook jargon
2786              cho    Choctaw
2787              chr    Cherokee
2788              chu    Church Slavic
2789              chv    Chuvash
2790              chy    Cheyenne
2791              cop    Coptic
2792              cor    Cornish
2793              cos    Corsican
2794              cpe    Creoles and Pidgins, English-based (Other)
2795              cpf    Creoles and Pidgins, French-based (Other)
2796              cpp    Creoles and Pidgins, Portuguese-based (Other)
2797              cre    Cree
2798              crp    Creoles and Pidgins (Other)
2799              cus    Cushitic (Other)
2800              cym    Welsh
2801              cze    Czech
2802              dak    Dakota
2803              dan    Danish
2804              del    Delaware
2805              deu    German
2806              din    Dinka
2807              div    Divehi
2808              doi    Dogri
2809              dra    Dravidian (Other)
2810              dua    Duala
2811              dum    Dutch, Middle (ca. 1050-1350)
2812              dut    Dutch
2813              dyu    Dyula
2814              dzo    Dzongkha
2815              efi    Efik
2816              egy    Egyptian (Ancient)
2817              eka    Ekajuk
2818              ell    Greek, Modern (1453-)
2819              elx    Elamite
2820              eng    English
2821              enm    English, Middle (ca. 1100-1500)
2822              epo    Esperanto
2823              esk    Eskimo (Other)
2824              esl    Spanish
2825              est    Estonian
2826              eus    Basque
2827              ewe    Ewe
2828              ewo    Ewondo
2829              fan    Fang
2830              fao    Faroese
2831              fas    Persian
2832              fat    Fanti
2833              fij    Fijian
2834              fin    Finnish
2835              fiu    Finno-Ugrian (Other)
2836              fon    Fon
2837              fra    French
2838              fre    French
2839              frm    French, Middle (ca. 1400-1600)
2840              fro    French, Old (842- ca. 1400)
2841              fry    Frisian
2842              ful    Fulah
2843              gaa    Ga
2844              gae    Gaelic (Scots)
2845              gai    Irish
2846              gay    Gayo
2847              gdh    Gaelic (Scots)
2848              gem    Germanic (Other)
2849              geo    Georgian
2850              ger    German
2851              gez    Geez
2852              gil    Gilbertese
2853              glg    Gallegan
2854              gmh    German, Middle High (ca. 1050-1500)
2855              goh    German, Old High (ca. 750-1050)
2856              gon    Gondi
2857              got    Gothic
2858              grb    Grebo
2859              grc    Greek, Ancient (to 1453)
2860              gre    Greek, Modern (1453-)
2861              grn    Guarani
2862              guj    Gujarati
2863              hai    Haida
2864              hau    Hausa
2865              haw    Hawaiian
2866              heb    Hebrew
2867              her    Herero
2868              hil    Hiligaynon
2869              him    Himachali
2870              hin    Hindi
2871              hmo    Hiri Motu
2872              hun    Hungarian
2873              hup    Hupa
2874              hye    Armenian
2875              iba    Iban
2876              ibo    Igbo
2877              ice    Icelandic
2878              ijo    Ijo
2879              iku    Inuktitut
2880              ilo    Iloko
2881              ina    Interlingua (International Auxiliary language Association)
2882              inc    Indic (Other)
2883              ind    Indonesian
2884              ine    Indo-European (Other)
2885              ine    Interlingue
2886              ipk    Inupiak
2887              ira    Iranian (Other)
2888              iri    Irish
2889              iro    Iroquoian uages
2890              isl    Icelandic
2891              ita    Italian
2892              jav    Javanese
2893              jaw    Javanese
2894              jpn    Japanese
2895              jpr    Judeo-Persian
2896              jrb    Judeo-Arabic
2897              kaa    Kara-Kalpak
2898              kab    Kabyle
2899              kac    Kachin
2900              kal    Greenlandic
2901              kam    Kamba
2902              kan    Kannada
2903              kar    Karen
2904              kas    Kashmiri
2905              kat    Georgian
2906              kau    Kanuri
2907              kaw    Kawi
2908              kaz    Kazakh
2909              kha    Khasi
2910              khi    Khoisan (Other)
2911              khm    Khmer
2912              kho    Khotanese
2913              kik    Kikuyu
2914              kin    Kinyarwanda
2915              kir    Kirghiz
2916              kok    Konkani
2917              kom    Komi
2918              kon    Kongo
2919              kor    Korean
2920              kpe    Kpelle
2921              kro    Kru
2922              kru    Kurukh
2923              kua    Kuanyama
2924              kum    Kumyk
2925              kur    Kurdish
2926              kus    Kusaie
2927              kut    Kutenai
2928              lad    Ladino
2929              lah    Lahnda
2930              lam    Lamba
2931              lao    Lao
2932              lat    Latin
2933              lav    Latvian
2934              lez    Lezghian
2935              lin    Lingala
2936              lit    Lithuanian
2937              lol    Mongo
2938              loz    Lozi
2939              ltz    Letzeburgesch
2940              lub    Luba-Katanga
2941              lug    Ganda
2942              lui    Luiseno
2943              lun    Lunda
2944              luo    Luo (Kenya and Tanzania)
2945              mac    Macedonian
2946              mad    Madurese
2947              mag    Magahi
2948              mah    Marshall
2949              mai    Maithili
2950              mak    Macedonian
2951              mak    Makasar
2952              mal    Malayalam
2953              man    Mandingo
2954              mao    Maori
2955              map    Austronesian (Other)
2956              mar    Marathi
2957              mas    Masai
2958              max    Manx
2959              may    Malay
2960              men    Mende
2961              mga    Irish, Middle (900 - 1200)
2962              mic    Micmac
2963              min    Minangkabau
2964              mis    Miscellaneous (Other)
2965              mkh    Mon-Kmer (Other)
2966              mlg    Malagasy
2967              mlt    Maltese
2968              mni    Manipuri
2969              mno    Manobo Languages
2970              moh    Mohawk
2971              mol    Moldavian
2972              mon    Mongolian
2973              mos    Mossi
2974              mri    Maori
2975              msa    Malay
2976              mul    Multiple Languages
2977              mun    Munda Languages
2978              mus    Creek
2979              mwr    Marwari
2980              mya    Burmese
2981              myn    Mayan Languages
2982              nah    Aztec
2983              nai    North American Indian (Other)
2984              nau    Nauru
2985              nav    Navajo
2986              nbl    Ndebele, South
2987              nde    Ndebele, North
2988              ndo    Ndongo
2989              nep    Nepali
2990              new    Newari
2991              nic    Niger-Kordofanian (Other)
2992              niu    Niuean
2993              nla    Dutch
2994              nno    Norwegian (Nynorsk)
2995              non    Norse, Old
2996              nor    Norwegian
2997              nso    Sotho, Northern
2998              nub    Nubian Languages
2999              nya    Nyanja
3000              nym    Nyamwezi
3001              nyn    Nyankole
3002              nyo    Nyoro
3003              nzi    Nzima
3004              oci    Langue d'Oc (post 1500)
3005              oji    Ojibwa
3006              ori    Oriya
3007              orm    Oromo
3008              osa    Osage
3009              oss    Ossetic
3010              ota    Turkish, Ottoman (1500 - 1928)
3011              oto    Otomian Languages
3012              paa    Papuan-Australian (Other)
3013              pag    Pangasinan
3014              pal    Pahlavi
3015              pam    Pampanga
3016              pan    Panjabi
3017              pap    Papiamento
3018              pau    Palauan
3019              peo    Persian, Old (ca 600 - 400 B.C.)
3020              per    Persian
3021              phn    Phoenician
3022              pli    Pali
3023              pol    Polish
3024              pon    Ponape
3025              por    Portuguese
3026              pra    Prakrit uages
3027              pro    Provencal, Old (to 1500)
3028              pus    Pushto
3029              que    Quechua
3030              raj    Rajasthani
3031              rar    Rarotongan
3032              roa    Romance (Other)
3033              roh    Rhaeto-Romance
3034              rom    Romany
3035              ron    Romanian
3036              rum    Romanian
3037              run    Rundi
3038              rus    Russian
3039              sad    Sandawe
3040              sag    Sango
3041              sah    Yakut
3042              sai    South American Indian (Other)
3043              sal    Salishan Languages
3044              sam    Samaritan Aramaic
3045              san    Sanskrit
3046              sco    Scots
3047              scr    Serbo-Croatian
3048              sel    Selkup
3049              sem    Semitic (Other)
3050              sga    Irish, Old (to 900)
3051              shn    Shan
3052              sid    Sidamo
3053              sin    Singhalese
3054              sio    Siouan Languages
3055              sit    Sino-Tibetan (Other)
3056              sla    Slavic (Other)
3057              slk    Slovak
3058              slo    Slovak
3059              slv    Slovenian
3060              smi    Sami Languages
3061              smo    Samoan
3062              sna    Shona
3063              snd    Sindhi
3064              sog    Sogdian
3065              som    Somali
3066              son    Songhai
3067              sot    Sotho, Southern
3068              spa    Spanish
3069              sqi    Albanian
3070              srd    Sardinian
3071              srr    Serer
3072              ssa    Nilo-Saharan (Other)
3073              ssw    Siswant
3074              ssw    Swazi
3075              suk    Sukuma
3076              sun    Sudanese
3077              sus    Susu
3078              sux    Sumerian
3079              sve    Swedish
3080              swa    Swahili
3081              swe    Swedish
3082              syr    Syriac
3083              tah    Tahitian
3084              tam    Tamil
3085              tat    Tatar
3086              tel    Telugu
3087              tem    Timne
3088              ter    Tereno
3089              tgk    Tajik
3090              tgl    Tagalog
3091              tha    Thai
3092              tib    Tibetan
3093              tig    Tigre
3094              tir    Tigrinya
3095              tiv    Tivi
3096              tli    Tlingit
3097              tmh    Tamashek
3098              tog    Tonga (Nyasa)
3099              ton    Tonga (Tonga Islands)
3100              tru    Truk
3101              tsi    Tsimshian
3102              tsn    Tswana
3103              tso    Tsonga
3104              tuk    Turkmen
3105              tum    Tumbuka
3106              tur    Turkish
3107              tut    Altaic (Other)
3108              twi    Twi
3109              tyv    Tuvinian
3110              uga    Ugaritic
3111              uig    Uighur
3112              ukr    Ukrainian
3113              umb    Umbundu
3114              und    Undetermined
3115              urd    Urdu
3116              uzb    Uzbek
3117              vai    Vai
3118              ven    Venda
3119              vie    Vietnamese
3120              vol    Volapük
3121              vot    Votic
3122              wak    Wakashan Languages
3123              wal    Walamo
3124              war    Waray
3125              was    Washo
3126              wel    Welsh
3127              wen    Sorbian Languages
3128              wol    Wolof
3129              xho    Xhosa
3130              yao    Yao
3131              yap    Yap
3132              yid    Yiddish
3133              yor    Yoruba
3134              zap    Zapotec
3135              zen    Zenaga
3136              zha    Zhuang
3137              zho    Chinese
3138              zul    Zulu
3139              zun    Zuni
3140  
3141          */
3142  
3143          return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
3144      }
3145  
3146      /**
3147       * @param int $index
3148       *
3149       * @return string
3150       */
3151  	public static function ETCOEventLookup($index) {
3152          if (($index >= 0x17) && ($index <= 0xDF)) {
3153              return 'reserved for future use';
3154          }
3155          if (($index >= 0xE0) && ($index <= 0xEF)) {
3156              return 'not predefined synch 0-F';
3157          }
3158          if (($index >= 0xF0) && ($index <= 0xFC)) {
3159              return 'reserved for future use';
3160          }
3161  
3162          static $EventLookup = array(
3163              0x00 => 'padding (has no meaning)',
3164              0x01 => 'end of initial silence',
3165              0x02 => 'intro start',
3166              0x03 => 'main part start',
3167              0x04 => 'outro start',
3168              0x05 => 'outro end',
3169              0x06 => 'verse start',
3170              0x07 => 'refrain start',
3171              0x08 => 'interlude start',
3172              0x09 => 'theme start',
3173              0x0A => 'variation start',
3174              0x0B => 'key change',
3175              0x0C => 'time change',
3176              0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3177              0x0E => 'sustained noise',
3178              0x0F => 'sustained noise end',
3179              0x10 => 'intro end',
3180              0x11 => 'main part end',
3181              0x12 => 'verse end',
3182              0x13 => 'refrain end',
3183              0x14 => 'theme end',
3184              0x15 => 'profanity',
3185              0x16 => 'profanity end',
3186              0xFD => 'audio end (start of silence)',
3187              0xFE => 'audio file ends',
3188              0xFF => 'one more byte of events follows'
3189          );
3190  
3191          return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
3192      }
3193  
3194      /**
3195       * @param int $index
3196       *
3197       * @return string
3198       */
3199  	public static function SYTLContentTypeLookup($index) {
3200          static $SYTLContentTypeLookup = array(
3201              0x00 => 'other',
3202              0x01 => 'lyrics',
3203              0x02 => 'text transcription',
3204              0x03 => 'movement/part name', // (e.g. 'Adagio')
3205              0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
3206              0x05 => 'chord',              // (e.g. 'Bb F Fsus')
3207              0x06 => 'trivia/\'pop up\' information',
3208              0x07 => 'URLs to webpages',
3209              0x08 => 'URLs to images'
3210          );
3211  
3212          return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
3213      }
3214  
3215      /**
3216       * @param int   $index
3217       * @param bool $returnarray
3218       *
3219       * @return array|string
3220       */
3221  	public static function APICPictureTypeLookup($index, $returnarray=false) {
3222          static $APICPictureTypeLookup = array(
3223              0x00 => 'Other',
3224              0x01 => '32x32 pixels \'file icon\' (PNG only)',
3225              0x02 => 'Other file icon',
3226              0x03 => 'Cover (front)',
3227              0x04 => 'Cover (back)',
3228              0x05 => 'Leaflet page',
3229              0x06 => 'Media (e.g. label side of CD)',
3230              0x07 => 'Lead artist/lead performer/soloist',
3231              0x08 => 'Artist/performer',
3232              0x09 => 'Conductor',
3233              0x0A => 'Band/Orchestra',
3234              0x0B => 'Composer',
3235              0x0C => 'Lyricist/text writer',
3236              0x0D => 'Recording Location',
3237              0x0E => 'During recording',
3238              0x0F => 'During performance',
3239              0x10 => 'Movie/video screen capture',
3240              0x11 => 'A bright coloured fish',
3241              0x12 => 'Illustration',
3242              0x13 => 'Band/artist logotype',
3243              0x14 => 'Publisher/Studio logotype'
3244          );
3245          if ($returnarray) {
3246              return $APICPictureTypeLookup;
3247          }
3248          return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
3249      }
3250  
3251      /**
3252       * @param int $index
3253       *
3254       * @return string
3255       */
3256  	public static function COMRReceivedAsLookup($index) {
3257          static $COMRReceivedAsLookup = array(
3258              0x00 => 'Other',
3259              0x01 => 'Standard CD album with other songs',
3260              0x02 => 'Compressed audio on CD',
3261              0x03 => 'File over the Internet',
3262              0x04 => 'Stream over the Internet',
3263              0x05 => 'As note sheets',
3264              0x06 => 'As note sheets in a book with other sheets',
3265              0x07 => 'Music on other media',
3266              0x08 => 'Non-musical merchandise'
3267          );
3268  
3269          return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
3270      }
3271  
3272      /**
3273       * @param int $index
3274       *
3275       * @return string
3276       */
3277  	public static function RVA2ChannelTypeLookup($index) {
3278          static $RVA2ChannelTypeLookup = array(
3279              0x00 => 'Other',
3280              0x01 => 'Master volume',
3281              0x02 => 'Front right',
3282              0x03 => 'Front left',
3283              0x04 => 'Back right',
3284              0x05 => 'Back left',
3285              0x06 => 'Front centre',
3286              0x07 => 'Back centre',
3287              0x08 => 'Subwoofer'
3288          );
3289  
3290          return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
3291      }
3292  
3293      /**
3294       * @param string $framename
3295       *
3296       * @return string
3297       */
3298  	public static function FrameNameLongLookup($framename) {
3299  
3300          $begin = __LINE__;
3301  
3302          /** This is not a comment!
3303  
3304              AENC    Audio encryption
3305              APIC    Attached picture
3306              ASPI    Audio seek point index
3307              BUF    Recommended buffer size
3308              CNT    Play counter
3309              COM    Comments
3310              COMM    Comments
3311              COMR    Commercial frame
3312              CRA    Audio encryption
3313              CRM    Encrypted meta frame
3314              ENCR    Encryption method registration
3315              EQU    Equalisation
3316              EQU2    Equalisation (2)
3317              EQUA    Equalisation
3318              ETC    Event timing codes
3319              ETCO    Event timing codes
3320              GEO    General encapsulated object
3321              GEOB    General encapsulated object
3322              GRID    Group identification registration
3323              IPL    Involved people list
3324              IPLS    Involved people list
3325              LINK    Linked information
3326              LNK    Linked information
3327              MCDI    Music CD identifier
3328              MCI    Music CD Identifier
3329              MLL    MPEG location lookup table
3330              MLLT    MPEG location lookup table
3331              OWNE    Ownership frame
3332              PCNT    Play counter
3333              PIC    Attached picture
3334              POP    Popularimeter
3335              POPM    Popularimeter
3336              POSS    Position synchronisation frame
3337              PRIV    Private frame
3338              RBUF    Recommended buffer size
3339              REV    Reverb
3340              RVA    Relative volume adjustment
3341              RVA2    Relative volume adjustment (2)
3342              RVAD    Relative volume adjustment
3343              RVRB    Reverb
3344              SEEK    Seek frame
3345              SIGN    Signature frame
3346              SLT    Synchronised lyric/text
3347              STC    Synced tempo codes
3348              SYLT    Synchronised lyric/text
3349              SYTC    Synchronised tempo codes
3350              TAL    Album/Movie/Show title
3351              TALB    Album/Movie/Show title
3352              TBP    BPM (Beats Per Minute)
3353              TBPM    BPM (beats per minute)
3354              TCM    Composer
3355              TCMP    Part of a compilation
3356              TCO    Content type
3357              TCOM    Composer
3358              TCON    Content type
3359              TCOP    Copyright message
3360              TCP    Part of a compilation
3361              TCR    Copyright message
3362              TDA    Date
3363              TDAT    Date
3364              TDEN    Encoding time
3365              TDLY    Playlist delay
3366              TDOR    Original release time
3367              TDRC    Recording time
3368              TDRL    Release time
3369              TDTG    Tagging time
3370              TDY    Playlist delay
3371              TEN    Encoded by
3372              TENC    Encoded by
3373              TEXT    Lyricist/Text writer
3374              TFLT    File type
3375              TFT    File type
3376              TIM    Time
3377              TIME    Time
3378              TIPL    Involved people list
3379              TIT1    Content group description
3380              TIT2    Title/songname/content description
3381              TIT3    Subtitle/Description refinement
3382              TKE    Initial key
3383              TKEY    Initial key
3384              TLA    Language(s)
3385              TLAN    Language(s)
3386              TLE    Length
3387              TLEN    Length
3388              TMCL    Musician credits list
3389              TMED    Media type
3390              TMOO    Mood
3391              TMT    Media type
3392              TOA    Original artist(s)/performer(s)
3393              TOAL    Original album/movie/show title
3394              TOF    Original filename
3395              TOFN    Original filename
3396              TOL    Original Lyricist(s)/text writer(s)
3397              TOLY    Original lyricist(s)/text writer(s)
3398              TOPE    Original artist(s)/performer(s)
3399              TOR    Original release year
3400              TORY    Original release year
3401              TOT    Original album/Movie/Show title
3402              TOWN    File owner/licensee
3403              TP1    Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3404              TP2    Band/Orchestra/Accompaniment
3405              TP3    Conductor/Performer refinement
3406              TP4    Interpreted, remixed, or otherwise modified by
3407              TPA    Part of a set
3408              TPB    Publisher
3409              TPE1    Lead performer(s)/Soloist(s)
3410              TPE2    Band/orchestra/accompaniment
3411              TPE3    Conductor/performer refinement
3412              TPE4    Interpreted, remixed, or otherwise modified by
3413              TPOS    Part of a set
3414              TPRO    Produced notice
3415              TPUB    Publisher
3416              TRC    ISRC (International Standard Recording Code)
3417              TRCK    Track number/Position in set
3418              TRD    Recording dates
3419              TRDA    Recording dates
3420              TRK    Track number/Position in set
3421              TRSN    Internet radio station name
3422              TRSO    Internet radio station owner
3423              TS2    Album-Artist sort order
3424              TSA    Album sort order
3425              TSC    Composer sort order
3426              TSI    Size
3427              TSIZ    Size
3428              TSO2    Album-Artist sort order
3429              TSOA    Album sort order
3430              TSOC    Composer sort order
3431              TSOP    Performer sort order
3432              TSOT    Title sort order
3433              TSP    Performer sort order
3434              TSRC    ISRC (international standard recording code)
3435              TSS    Software/hardware and settings used for encoding
3436              TSSE    Software/Hardware and settings used for encoding
3437              TSST    Set subtitle
3438              TST    Title sort order
3439              TT1    Content group description
3440              TT2    Title/Songname/Content description
3441              TT3    Subtitle/Description refinement
3442              TXT    Lyricist/text writer
3443              TXX    User defined text information frame
3444              TXXX    User defined text information frame
3445              TYE    Year
3446              TYER    Year
3447              UFI    Unique file identifier
3448              UFID    Unique file identifier
3449              ULT    Unsynchronised lyric/text transcription
3450              USER    Terms of use
3451              USLT    Unsynchronised lyric/text transcription
3452              WAF    Official audio file webpage
3453              WAR    Official artist/performer webpage
3454              WAS    Official audio source webpage
3455              WCM    Commercial information
3456              WCOM    Commercial information
3457              WCOP    Copyright/Legal information
3458              WCP    Copyright/Legal information
3459              WOAF    Official audio file webpage
3460              WOAR    Official artist/performer webpage
3461              WOAS    Official audio source webpage
3462              WORS    Official Internet radio station homepage
3463              WPAY    Payment
3464              WPB    Publishers official webpage
3465              WPUB    Publishers official webpage
3466              WXX    User defined URL link frame
3467              WXXX    User defined URL link frame
3468              TFEA    Featured Artist
3469              TSTU    Recording Studio
3470              rgad    Replay Gain Adjustment
3471  
3472          */
3473  
3474          return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3475  
3476          // Last three:
3477          // from Helium2 [www.helium2.com]
3478          // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3479      }
3480  
3481      /**
3482       * @param string $framename
3483       *
3484       * @return string
3485       */
3486  	public static function FrameNameShortLookup($framename) {
3487  
3488          $begin = __LINE__;
3489  
3490          /** This is not a comment!
3491  
3492              AENC    audio_encryption
3493              APIC    attached_picture
3494              ASPI    audio_seek_point_index
3495              BUF    recommended_buffer_size
3496              CNT    play_counter
3497              COM    comment
3498              COMM    comment
3499              COMR    commercial_frame
3500              CRA    audio_encryption
3501              CRM    encrypted_meta_frame
3502              ENCR    encryption_method_registration
3503              EQU    equalisation
3504              EQU2    equalisation
3505              EQUA    equalisation
3506              ETC    event_timing_codes
3507              ETCO    event_timing_codes
3508              GEO    general_encapsulated_object
3509              GEOB    general_encapsulated_object
3510              GRID    group_identification_registration
3511              IPL    involved_people_list
3512              IPLS    involved_people_list
3513              LINK    linked_information
3514              LNK    linked_information
3515              MCDI    music_cd_identifier
3516              MCI    music_cd_identifier
3517              MLL    mpeg_location_lookup_table
3518              MLLT    mpeg_location_lookup_table
3519              OWNE    ownership_frame
3520              PCNT    play_counter
3521              PIC    attached_picture
3522              POP    popularimeter
3523              POPM    popularimeter
3524              POSS    position_synchronisation_frame
3525              PRIV    private_frame
3526              RBUF    recommended_buffer_size
3527              REV    reverb
3528              RVA    relative_volume_adjustment
3529              RVA2    relative_volume_adjustment
3530              RVAD    relative_volume_adjustment
3531              RVRB    reverb
3532              SEEK    seek_frame
3533              SIGN    signature_frame
3534              SLT    synchronised_lyric
3535              STC    synced_tempo_codes
3536              SYLT    synchronised_lyric
3537              SYTC    synchronised_tempo_codes
3538              TAL    album
3539              TALB    album
3540              TBP    bpm
3541              TBPM    bpm
3542              TCM    composer
3543              TCMP    part_of_a_compilation
3544              TCO    genre
3545              TCOM    composer
3546              TCON    genre
3547              TCOP    copyright_message
3548              TCP    part_of_a_compilation
3549              TCR    copyright_message
3550              TDA    date
3551              TDAT    date
3552              TDEN    encoding_time
3553              TDLY    playlist_delay
3554              TDOR    original_release_time
3555              TDRC    recording_time
3556              TDRL    release_time
3557              TDTG    tagging_time
3558              TDY    playlist_delay
3559              TEN    encoded_by
3560              TENC    encoded_by
3561              TEXT    lyricist
3562              TFLT    file_type
3563              TFT    file_type
3564              TIM    time
3565              TIME    time
3566              TIPL    involved_people_list
3567              TIT1    content_group_description
3568              TIT2    title
3569              TIT3    subtitle
3570              TKE    initial_key
3571              TKEY    initial_key
3572              TLA    language
3573              TLAN    language
3574              TLE    length
3575              TLEN    length
3576              TMCL    musician_credits_list
3577              TMED    media_type
3578              TMOO    mood
3579              TMT    media_type
3580              TOA    original_artist
3581              TOAL    original_album
3582              TOF    original_filename
3583              TOFN    original_filename
3584              TOL    original_lyricist
3585              TOLY    original_lyricist
3586              TOPE    original_artist
3587              TOR    original_year
3588              TORY    original_year
3589              TOT    original_album
3590              TOWN    file_owner
3591              TP1    artist
3592              TP2    band
3593              TP3    conductor
3594              TP4    remixer
3595              TPA    part_of_a_set
3596              TPB    publisher
3597              TPE1    artist
3598              TPE2    band
3599              TPE3    conductor
3600              TPE4    remixer
3601              TPOS    part_of_a_set
3602              TPRO    produced_notice
3603              TPUB    publisher
3604              TRC    isrc
3605              TRCK    track_number
3606              TRD    recording_dates
3607              TRDA    recording_dates
3608              TRK    track_number
3609              TRSN    internet_radio_station_name
3610              TRSO    internet_radio_station_owner
3611              TS2    album_artist_sort_order
3612              TSA    album_sort_order
3613              TSC    composer_sort_order
3614              TSI    size
3615              TSIZ    size
3616              TSO2    album_artist_sort_order
3617              TSOA    album_sort_order
3618              TSOC    composer_sort_order
3619              TSOP    performer_sort_order
3620              TSOT    title_sort_order
3621              TSP    performer_sort_order
3622              TSRC    isrc
3623              TSS    encoder_settings
3624              TSSE    encoder_settings
3625              TSST    set_subtitle
3626              TST    title_sort_order
3627              TT1    content_group_description
3628              TT2    title
3629              TT3    subtitle
3630              TXT    lyricist
3631              TXX    text
3632              TXXX    text
3633              TYE    year
3634              TYER    year
3635              UFI    unique_file_identifier
3636              UFID    unique_file_identifier
3637              ULT    unsynchronised_lyric
3638              USER    terms_of_use
3639              USLT    unsynchronised_lyric
3640              WAF    url_file
3641              WAR    url_artist
3642              WAS    url_source
3643              WCM    commercial_information
3644              WCOM    commercial_information
3645              WCOP    copyright
3646              WCP    copyright
3647              WOAF    url_file
3648              WOAR    url_artist
3649              WOAS    url_source
3650              WORS    url_station
3651              WPAY    url_payment
3652              WPB    url_publisher
3653              WPUB    url_publisher
3654              WXX    url_user
3655              WXXX    url_user
3656              TFEA    featured_artist
3657              TSTU    recording_studio
3658              rgad    replay_gain_adjustment
3659  
3660          */
3661  
3662          return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3663      }
3664  
3665      /**
3666       * @param string $encoding
3667       *
3668       * @return string
3669       */
3670  	public static function TextEncodingTerminatorLookup($encoding) {
3671          // http://www.id3.org/id3v2.4.0-structure.txt
3672          // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3673          static $TextEncodingTerminatorLookup = array(
3674              0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
3675              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.
3676              2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3677              3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
3678              255 => "\x00\x00"
3679          );
3680          return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
3681      }
3682  
3683      /**
3684       * @param int $encoding
3685       *
3686       * @return string
3687       */
3688  	public static function TextEncodingNameLookup($encoding) {
3689          // http://www.id3.org/id3v2.4.0-structure.txt
3690          // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3691          static $TextEncodingNameLookup = array(
3692              0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
3693              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.
3694              2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3695              3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
3696              255 => 'UTF-16BE'
3697          );
3698          return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3699      }
3700  
3701      /**
3702       * @param string $string
3703       * @param string $terminator
3704       *
3705       * @return string
3706       */
3707  	public static function RemoveStringTerminator($string, $terminator) {
3708          // 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.
3709          // https://github.com/JamesHeinrich/getID3/issues/121
3710          // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
3711          if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
3712              $string = substr($string, 0, -strlen($terminator));
3713          }
3714          return $string;
3715      }
3716  
3717      /**
3718       * @param string $string
3719       *
3720       * @return string
3721       */
3722  	public static function MakeUTF16emptyStringEmpty($string) {
3723          if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
3724              // if string only contains a BOM or terminator then make it actually an empty string
3725              $string = '';
3726          }
3727          return $string;
3728      }
3729  
3730      /**
3731       * @param string $framename
3732       * @param int    $id3v2majorversion
3733       *
3734       * @return bool|int
3735       */
3736  	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3737          switch ($id3v2majorversion) {
3738              case 2:
3739                  return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3740  
3741              case 3:
3742              case 4:
3743                  return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3744          }
3745          return false;
3746      }
3747  
3748      /**
3749       * @param string $numberstring
3750       * @param bool   $allowdecimal
3751       * @param bool   $allownegative
3752       *
3753       * @return bool
3754       */
3755  	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3756          for ($i = 0; $i < strlen($numberstring); $i++) {
3757              if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
3758                  if (($numberstring[$i] == '.') && $allowdecimal) {
3759                      // allowed
3760                  } elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
3761                      // allowed
3762                  } else {
3763                      return false;
3764                  }
3765              }
3766          }
3767          return true;
3768      }
3769  
3770      /**
3771       * @param string $datestamp
3772       *
3773       * @return bool
3774       */
3775  	public static function IsValidDateStampString($datestamp) {
3776          if (strlen($datestamp) != 8) {
3777              return false;
3778          }
3779          if (!self::IsANumber($datestamp, false)) {
3780              return false;
3781          }
3782          $year  = substr($datestamp, 0, 4);
3783          $month = substr($datestamp, 4, 2);
3784          $day   = substr($datestamp, 6, 2);
3785          if (($year == 0) || ($month == 0) || ($day == 0)) {
3786              return false;
3787          }
3788          if ($month > 12) {
3789              return false;
3790          }
3791          if ($day > 31) {
3792              return false;
3793          }
3794          if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3795              return false;
3796          }
3797          if (($day > 29) && ($month == 2)) {
3798              return false;
3799          }
3800          return true;
3801      }
3802  
3803      /**
3804       * @param int $majorversion
3805       *
3806       * @return int
3807       */
3808  	public static function ID3v2HeaderLength($majorversion) {
3809          return (($majorversion == 2) ? 6 : 10);
3810      }
3811  
3812      /**
3813       * @param string $frame_name
3814       *
3815       * @return string|false
3816       */
3817  	public static function ID3v22iTunesBrokenFrameName($frame_name) {
3818          // iTunes (multiple versions) has been known to write ID3v2.3 style frames
3819          // but use ID3v2.2 frame names, right-padded using either [space] or [null]
3820          // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3821          // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3822          static $ID3v22_iTunes_BrokenFrames = array(
3823              'BUF' => 'RBUF', // Recommended buffer size
3824              'CNT' => 'PCNT', // Play counter
3825              'COM' => 'COMM', // Comments
3826              'CRA' => 'AENC', // Audio encryption
3827              'EQU' => 'EQUA', // Equalisation
3828              'ETC' => 'ETCO', // Event timing codes
3829              'GEO' => 'GEOB', // General encapsulated object
3830              'IPL' => 'IPLS', // Involved people list
3831              'LNK' => 'LINK', // Linked information
3832              'MCI' => 'MCDI', // Music CD identifier
3833              'MLL' => 'MLLT', // MPEG location lookup table
3834              'PIC' => 'APIC', // Attached picture
3835              'POP' => 'POPM', // Popularimeter
3836              'REV' => 'RVRB', // Reverb
3837              'RVA' => 'RVAD', // Relative volume adjustment
3838              'SLT' => 'SYLT', // Synchronised lyric/text
3839              'STC' => 'SYTC', // Synchronised tempo codes
3840              'TAL' => 'TALB', // Album/Movie/Show title
3841              'TBP' => 'TBPM', // BPM (beats per minute)
3842              'TCM' => 'TCOM', // Composer
3843              'TCO' => 'TCON', // Content type
3844              'TCP' => 'TCMP', // Part of a compilation
3845              'TCR' => 'TCOP', // Copyright message
3846              'TDA' => 'TDAT', // Date
3847              'TDY' => 'TDLY', // Playlist delay
3848              'TEN' => 'TENC', // Encoded by
3849              'TFT' => 'TFLT', // File type
3850              'TIM' => 'TIME', // Time
3851              'TKE' => 'TKEY', // Initial key
3852              'TLA' => 'TLAN', // Language(s)
3853              'TLE' => 'TLEN', // Length
3854              'TMT' => 'TMED', // Media type
3855              'TOA' => 'TOPE', // Original artist(s)/performer(s)
3856              'TOF' => 'TOFN', // Original filename
3857              'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
3858              'TOR' => 'TORY', // Original release year
3859              'TOT' => 'TOAL', // Original album/movie/show title
3860              'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
3861              'TP2' => 'TPE2', // Band/orchestra/accompaniment
3862              'TP3' => 'TPE3', // Conductor/performer refinement
3863              'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
3864              'TPA' => 'TPOS', // Part of a set
3865              'TPB' => 'TPUB', // Publisher
3866              'TRC' => 'TSRC', // ISRC (international standard recording code)
3867              'TRD' => 'TRDA', // Recording dates
3868              'TRK' => 'TRCK', // Track number/Position in set
3869              'TS2' => 'TSO2', // Album-Artist sort order
3870              'TSA' => 'TSOA', // Album sort order
3871              'TSC' => 'TSOC', // Composer sort order
3872              'TSI' => 'TSIZ', // Size
3873              'TSP' => 'TSOP', // Performer sort order
3874              'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
3875              'TST' => 'TSOT', // Title sort order
3876              'TT1' => 'TIT1', // Content group description
3877              'TT2' => 'TIT2', // Title/songname/content description
3878              'TT3' => 'TIT3', // Subtitle/Description refinement
3879              'TXT' => 'TEXT', // Lyricist/Text writer
3880              'TXX' => 'TXXX', // User defined text information frame
3881              'TYE' => 'TYER', // Year
3882              'UFI' => 'UFID', // Unique file identifier
3883              'ULT' => 'USLT', // Unsynchronised lyric/text transcription
3884              'WAF' => 'WOAF', // Official audio file webpage
3885              'WAR' => 'WOAR', // Official artist/performer webpage
3886              'WAS' => 'WOAS', // Official audio source webpage
3887              'WCM' => 'WCOM', // Commercial information
3888              'WCP' => 'WCOP', // Copyright/Legal in