[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Wed Sep 9 08:20:27 2026 Cross-referenced by PHPXref