[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ID3/ -> module.tag.id3v1.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.id3v1.php                                        //
  12  // module for analyzing ID3v1 tags                             //
  13  // dependencies: NONE                                          //
  14  //                                                            ///
  15  /////////////////////////////////////////////////////////////////
  16  
  17  if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
  18      exit;
  19  }
  20  
  21  class getid3_id3v1 extends getid3_handler
  22  {
  23      /**
  24       * @return bool
  25       */
  26  	public function Analyze() {
  27          $info = &$this->getid3->info;
  28  
  29          if (!getid3_lib::intValueSupported($info['filesize'])) {
  30              $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
  31              return false;
  32          }
  33  
  34          if($info['filesize'] < 256) {
  35              $this->fseek(-128, SEEK_END);
  36              $preid3v1 = '';
  37              $id3v1tag = $this->fread(128);
  38          } else {
  39              $this->fseek(-256, SEEK_END);
  40              $preid3v1 = $this->fread(128);
  41              $id3v1tag = $this->fread(128);
  42          }
  43  
  44  
  45          if (substr($id3v1tag, 0, 3) == 'TAG') {
  46  
  47              $info['avdataend'] = $info['filesize'] - 128;
  48  
  49              $ParsedID3v1            = array();
  50              $ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
  51              $ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
  52              $ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
  53              $ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
  54              $ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
  55              $ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
  56  
  57              // If second-last byte of comment field is null and last byte of comment field is non-null
  58              // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
  59              if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) {
  60                  $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29,  1));
  61                  $ParsedID3v1['comment']      =     substr($ParsedID3v1['comment'],  0, 28);
  62              }
  63              $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
  64  
  65              $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
  66              if (!empty($ParsedID3v1['genre'])) {
  67                  unset($ParsedID3v1['genreid']);
  68              }
  69              if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) {
  70                  unset($ParsedID3v1['genre']);
  71              }
  72  
  73              foreach ($ParsedID3v1 as $key => $value) {
  74                  $ParsedID3v1['comments'][$key][0] = $value;
  75              }
  76              $ID3v1encoding = $this->getid3->encoding_id3v1;
  77              if ($this->getid3->encoding_id3v1_autodetect) {
  78                  // ID3v1 encoding detection hack START
  79                  // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
  80                  // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
  81                  foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
  82                      foreach ($valuearray as $key => $value) {
  83                          if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
  84                              foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
  85                                  if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
  86                                      $ID3v1encoding = $id3v1_bad_encoding;
  87                                      $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
  88                                      break 3;
  89                                  } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
  90                                      $ID3v1encoding = $id3v1_bad_encoding;
  91                                      $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
  92                                      break 3;
  93                                  }
  94                              }
  95                          }
  96                      }
  97                  }
  98                  // ID3v1 encoding detection hack END
  99              }
 100  
 101              // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
 102              $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
 103                                              $ParsedID3v1['title'],
 104                                              $ParsedID3v1['artist'],
 105                                              $ParsedID3v1['album'],
 106                                              $ParsedID3v1['year'],
 107                                              (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
 108                                              $ParsedID3v1['comment'],
 109                                              (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : ''));
 110              $ParsedID3v1['padding_valid'] = true;
 111              if ($id3v1tag !== $GoodFormatID3v1tag) {
 112                  $ParsedID3v1['padding_valid'] = false;
 113                  $this->warning('Some ID3v1 fields do not use NULL characters for padding');
 114              }
 115  
 116              $ParsedID3v1['tag_offset_end']   = $info['filesize'];
 117              $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
 118  
 119              $info['id3v1'] = $ParsedID3v1;
 120              $info['id3v1']['encoding'] = $ID3v1encoding;
 121          }
 122  
 123          if (substr($preid3v1, 0, 3) == 'TAG') {
 124              // The way iTunes handles tags is, well, brain-damaged.
 125              // It completely ignores v1 if ID3v2 is present.
 126              // This goes as far as adding a new v1 tag *even if there already is one*
 127  
 128              // A suspected double-ID3v1 tag has been detected, but it could be that
 129              // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
 130              if (substr($preid3v1, 96, 8) == 'APETAGEX') {
 131                  // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
 132              } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
 133                  // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
 134              } else {
 135                  // APE and Lyrics3 footers not found - assume double ID3v1
 136                  $this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
 137                  $info['avdataend'] -= 128;
 138              }
 139          }
 140  
 141          return true;
 142      }
 143  
 144      /**
 145       * @param string $str
 146       *
 147       * @return string
 148       */
 149  	public static function cutfield($str) {
 150          return trim(substr($str, 0, strcspn($str, "\x00")));
 151      }
 152  
 153      /**
 154       * @param bool $allowSCMPXextended
 155       *
 156       * @return string[]
 157       */
 158  	public static function ArrayOfGenres($allowSCMPXextended=false) {
 159          static $GenreLookup = array(
 160              0    => 'Blues',
 161              1    => 'Classic Rock',
 162              2    => 'Country',
 163              3    => 'Dance',
 164              4    => 'Disco',
 165              5    => 'Funk',
 166              6    => 'Grunge',
 167              7    => 'Hip-Hop',
 168              8    => 'Jazz',
 169              9    => 'Metal',
 170              10   => 'New Age',
 171              11   => 'Oldies',
 172              12   => 'Other',
 173              13   => 'Pop',
 174              14   => 'R&B',
 175              15   => 'Rap',
 176              16   => 'Reggae',
 177              17   => 'Rock',
 178              18   => 'Techno',
 179              19   => 'Industrial',
 180              20   => 'Alternative',
 181              21   => 'Ska',
 182              22   => 'Death Metal',
 183              23   => 'Pranks',
 184              24   => 'Soundtrack',
 185              25   => 'Euro-Techno',
 186              26   => 'Ambient',
 187              27   => 'Trip-Hop',
 188              28   => 'Vocal',
 189              29   => 'Jazz+Funk',
 190              30   => 'Fusion',
 191              31   => 'Trance',
 192              32   => 'Classical',
 193              33   => 'Instrumental',
 194              34   => 'Acid',
 195              35   => 'House',
 196              36   => 'Game',
 197              37   => 'Sound Clip',
 198              38   => 'Gospel',
 199              39   => 'Noise',
 200              40   => 'Alt. Rock',
 201              41   => 'Bass',
 202              42   => 'Soul',
 203              43   => 'Punk',
 204              44   => 'Space',
 205              45   => 'Meditative',
 206              46   => 'Instrumental Pop',
 207              47   => 'Instrumental Rock',
 208              48   => 'Ethnic',
 209              49   => 'Gothic',
 210              50   => 'Darkwave',
 211              51   => 'Techno-Industrial',
 212              52   => 'Electronic',
 213              53   => 'Pop-Folk',
 214              54   => 'Eurodance',
 215              55   => 'Dream',
 216              56   => 'Southern Rock',
 217              57   => 'Comedy',
 218              58   => 'Cult',
 219              59   => 'Gangsta Rap',
 220              60   => 'Top 40',
 221              61   => 'Christian Rap',
 222              62   => 'Pop/Funk',
 223              63   => 'Jungle',
 224              64   => 'Native American',
 225              65   => 'Cabaret',
 226              66   => 'New Wave',
 227              67   => 'Psychedelic',
 228              68   => 'Rave',
 229              69   => 'Showtunes',
 230              70   => 'Trailer',
 231              71   => 'Lo-Fi',
 232              72   => 'Tribal',
 233              73   => 'Acid Punk',
 234              74   => 'Acid Jazz',
 235              75   => 'Polka',
 236              76   => 'Retro',
 237              77   => 'Musical',
 238              78   => 'Rock & Roll',
 239              79   => 'Hard Rock',
 240              80   => 'Folk',
 241              81   => 'Folk/Rock',
 242              82   => 'National Folk',
 243              83   => 'Swing',
 244              84   => 'Fast-Fusion',
 245              85   => 'Bebob',
 246              86   => 'Latin',
 247              87   => 'Revival',
 248              88   => 'Celtic',
 249              89   => 'Bluegrass',
 250              90   => 'Avantgarde',
 251              91   => 'Gothic Rock',
 252              92   => 'Progressive Rock',
 253              93   => 'Psychedelic Rock',
 254              94   => 'Symphonic Rock',
 255              95   => 'Slow Rock',
 256              96   => 'Big Band',
 257              97   => 'Chorus',
 258              98   => 'Easy Listening',
 259              99   => 'Acoustic',
 260              100  => 'Humour',
 261              101  => 'Speech',
 262              102  => 'Chanson',
 263              103  => 'Opera',
 264              104  => 'Chamber Music',
 265              105  => 'Sonata',
 266              106  => 'Symphony',
 267              107  => 'Booty Bass',
 268              108  => 'Primus',
 269              109  => 'Porn Groove',
 270              110  => 'Satire',
 271              111  => 'Slow Jam',
 272              112  => 'Club',
 273              113  => 'Tango',
 274              114  => 'Samba',
 275              115  => 'Folklore',
 276              116  => 'Ballad',
 277              117  => 'Power Ballad',
 278              118  => 'Rhythmic Soul',
 279              119  => 'Freestyle',
 280              120  => 'Duet',
 281              121  => 'Punk Rock',
 282              122  => 'Drum Solo',
 283              123  => 'A Cappella',
 284              124  => 'Euro-House',
 285              125  => 'Dance Hall',
 286              126  => 'Goa',
 287              127  => 'Drum & Bass',
 288              128  => 'Club-House',
 289              129  => 'Hardcore',
 290              130  => 'Terror',
 291              131  => 'Indie',
 292              132  => 'BritPop',
 293              133  => 'Negerpunk',
 294              134  => 'Polsk Punk',
 295              135  => 'Beat',
 296              136  => 'Christian Gangsta Rap',
 297              137  => 'Heavy Metal',
 298              138  => 'Black Metal',
 299              139  => 'Crossover',
 300              140  => 'Contemporary Christian',
 301              141  => 'Christian Rock',
 302              142  => 'Merengue',
 303              143  => 'Salsa',
 304              144  => 'Thrash Metal',
 305              145  => 'Anime',
 306              146  => 'JPop',
 307              147  => 'Synthpop',
 308              148 => 'Abstract',
 309              149 => 'Art Rock',
 310              150 => 'Baroque',
 311              151 => 'Bhangra',
 312              152 => 'Big Beat',
 313              153 => 'Breakbeat',
 314              154 => 'Chillout',
 315              155 => 'Downtempo',
 316              156 => 'Dub',
 317              157 => 'EBM',
 318              158 => 'Eclectic',
 319              159 => 'Electro',
 320              160 => 'Electroclash',
 321              161 => 'Emo',
 322              162 => 'Experimental',
 323              163 => 'Garage',
 324              164 => 'Global',
 325              165 => 'IDM',
 326              166 => 'Illbient',
 327              167 => 'Industro-Goth',
 328              168 => 'Jam Band',
 329              169 => 'Krautrock',
 330              170 => 'Leftfield',
 331              171 => 'Lounge',
 332              172 => 'Math Rock',
 333              173 => 'New Romantic',
 334              174 => 'Nu-Breakz',
 335              175 => 'Post-Punk',
 336              176 => 'Post-Rock',
 337              177 => 'Psytrance',
 338              178 => 'Shoegaze',
 339              179 => 'Space Rock',
 340              180 => 'Trop Rock',
 341              181 => 'World Music',
 342              182 => 'Neoclassical',
 343              183 => 'Audiobook',
 344              184 => 'Audio Theatre',
 345              185 => 'Neue Deutsche Welle',
 346              186 => 'Podcast',
 347              187 => 'Indie-Rock',
 348              188 => 'G-Funk',
 349              189 => 'Dubstep',
 350              190 => 'Garage Rock',
 351              191 => 'Psybient',
 352  
 353              255  => 'Unknown',
 354  
 355              'CR' => 'Cover',
 356              'RX' => 'Remix'
 357          );
 358  
 359          static $GenreLookupSCMPX = array();
 360          if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
 361              $GenreLookupSCMPX = $GenreLookup;
 362              // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
 363              // Extended ID3v1 genres invented by SCMPX
 364              // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
 365              $GenreLookupSCMPX[240] = 'Sacred';
 366              $GenreLookupSCMPX[241] = 'Northern Europe';
 367              $GenreLookupSCMPX[242] = 'Irish & Scottish';
 368              $GenreLookupSCMPX[243] = 'Scotland';
 369              $GenreLookupSCMPX[244] = 'Ethnic Europe';
 370              $GenreLookupSCMPX[245] = 'Enka';
 371              $GenreLookupSCMPX[246] = 'Children\'s Song';
 372              $GenreLookupSCMPX[247] = 'Japanese Sky';
 373              $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
 374              $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
 375              $GenreLookupSCMPX[250] = 'Japanese J-POP';
 376              $GenreLookupSCMPX[251] = 'Japanese Seiyu';
 377              $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
 378              $GenreLookupSCMPX[253] = 'Japanese Moemoe';
 379              $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
 380              //$GenreLookupSCMPX[255] = 'Japanese Anime';
 381          }
 382  
 383          return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
 384      }
 385  
 386      /**
 387       * @param string $genreid
 388       * @param bool   $allowSCMPXextended
 389       *
 390       * @return string|false
 391       */
 392  	public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
 393          switch ($genreid) {
 394              case 'RX':
 395              case 'CR':
 396                  break;
 397              default:
 398                  if (!is_numeric($genreid)) {
 399                      return false;
 400                  }
 401                  $genreid = intval($genreid); // to handle 3 or '3' or '03'
 402                  break;
 403          }
 404          $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
 405          return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
 406      }
 407  
 408      /**
 409       * @param string $genre
 410       * @param bool   $allowSCMPXextended
 411       *
 412       * @return string|false
 413       */
 414  	public static function LookupGenreID($genre, $allowSCMPXextended=false) {
 415          $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
 416          $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
 417          foreach ($GenreLookup as $key => $value) {
 418              if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
 419                  return $key;
 420              }
 421          }
 422          return false;
 423      }
 424  
 425      /**
 426       * @param string $OriginalGenre
 427       *
 428       * @return string|false
 429       */
 430  	public static function StandardiseID3v1GenreName($OriginalGenre) {
 431          if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
 432              return self::LookupGenreName($GenreID);
 433          }
 434          return $OriginalGenre;
 435      }
 436  
 437      /**
 438       * @param string     $title
 439       * @param string     $artist
 440       * @param string     $album
 441       * @param string     $year
 442       * @param int        $genreid
 443       * @param string     $comment
 444       * @param int|string $track
 445       *
 446       * @return string
 447       */
 448  	public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
 449          $ID3v1Tag  = 'TAG';
 450          $ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
 451          $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
 452          $ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
 453          $ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
 454          if (!empty($track) && ($track > 0) && ($track <= 255)) {
 455              $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
 456              $ID3v1Tag .= "\x00";
 457              if (gettype($track) == 'string') {
 458                  $track = (int) $track;
 459              }
 460              $ID3v1Tag .= chr($track);
 461          } else {
 462              $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
 463          }
 464          if (($genreid < 0) || ($genreid > 147)) {
 465              $genreid = 255; // 'unknown' genre
 466          }
 467          switch (gettype($genreid)) {
 468              case 'string':
 469              case 'integer':
 470                  $ID3v1Tag .= chr(intval($genreid));
 471                  break;
 472              default:
 473                  $ID3v1Tag .= chr(255); // 'unknown' genre
 474                  break;
 475          }
 476  
 477          return $ID3v1Tag;
 478      }
 479  
 480  }


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref