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


Generated : Wed Apr 15 08:20:10 2026 Cross-referenced by PHPXref