File indexing completed on 2024-12-22 05:33:14

0001 <?php
0002 /////////////////////////////////////////////////////////////////
0003 /// getID3() by James Heinrich <info@getid3.org>               //
0004 //  available at http://getid3.sourceforge.net                 //
0005 //            or http://www.getid3.org                         //
0006 //          also https://github.com/JamesHeinrich/getID3       //
0007 /////////////////////////////////////////////////////////////////
0008 // See readme.txt for more details                             //
0009 /////////////////////////////////////////////////////////////////
0010 //                                                             //
0011 // module.tag.id3v1.php                                        //
0012 // module for analyzing ID3v1 tags                             //
0013 // dependencies: NONE                                          //
0014 //                                                            ///
0015 /////////////////////////////////////////////////////////////////
0016 
0017 
0018 class getid3_id3v1 extends getid3_handler
0019 {
0020 
0021   public function Analyze() {
0022     $info = &$this->getid3->info;
0023 
0024     if (!getid3_lib::intValueSupported($info['filesize'])) {
0025       $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
0026       return false;
0027     }
0028 
0029     $this->fseek(-256, SEEK_END);
0030     $preid3v1 = $this->fread(128);
0031     $id3v1tag = $this->fread(128);
0032 
0033     if (substr($id3v1tag, 0, 3) == 'TAG') {
0034 
0035       $info['avdataend'] = $info['filesize'] - 128;
0036 
0037       $ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
0038       $ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
0039       $ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
0040       $ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
0041       $ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
0042       $ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
0043 
0044       // If second-last byte of comment field is null and last byte of comment field is non-null
0045       // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
0046       if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
0047         $ParsedID3v1['track']   = ord(substr($ParsedID3v1['comment'], 29,  1));
0048         $ParsedID3v1['comment'] =     substr($ParsedID3v1['comment'],  0, 28);
0049       }
0050       $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
0051 
0052       $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
0053       if (!empty($ParsedID3v1['genre'])) {
0054         unset($ParsedID3v1['genreid']);
0055       }
0056       if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
0057         unset($ParsedID3v1['genre']);
0058       }
0059 
0060       foreach ($ParsedID3v1 as $key => $value) {
0061         $ParsedID3v1['comments'][$key][0] = $value;
0062       }
0063 
0064       // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
0065       $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
0066                       $ParsedID3v1['title'],
0067                       $ParsedID3v1['artist'],
0068                       $ParsedID3v1['album'],
0069                       $ParsedID3v1['year'],
0070                       (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
0071                       $ParsedID3v1['comment'],
0072                       (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
0073       $ParsedID3v1['padding_valid'] = true;
0074       if ($id3v1tag !== $GoodFormatID3v1tag) {
0075         $ParsedID3v1['padding_valid'] = false;
0076         $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
0077       }
0078 
0079       $ParsedID3v1['tag_offset_end']   = $info['filesize'];
0080       $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
0081 
0082       $info['id3v1'] = $ParsedID3v1;
0083     }
0084 
0085     if (substr($preid3v1, 0, 3) == 'TAG') {
0086       // The way iTunes handles tags is, well, brain-damaged.
0087       // It completely ignores v1 if ID3v2 is present.
0088       // This goes as far as adding a new v1 tag *even if there already is one*
0089 
0090       // A suspected double-ID3v1 tag has been detected, but it could be that
0091       // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
0092       if (substr($preid3v1, 96, 8) == 'APETAGEX') {
0093         // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
0094       } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
0095         // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
0096       } else {
0097         // APE and Lyrics3 footers not found - assume double ID3v1
0098         $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
0099         $info['avdataend'] -= 128;
0100       }
0101     }
0102 
0103     return true;
0104   }
0105 
0106   public static function cutfield($str) {
0107     return trim(substr($str, 0, strcspn($str, "\x00")));
0108   }
0109 
0110   public static function ArrayOfGenres($allowSCMPXextended=false) {
0111     static $GenreLookup = array(
0112       0    => 'Blues',
0113       1    => 'Classic Rock',
0114       2    => 'Country',
0115       3    => 'Dance',
0116       4    => 'Disco',
0117       5    => 'Funk',
0118       6    => 'Grunge',
0119       7    => 'Hip-Hop',
0120       8    => 'Jazz',
0121       9    => 'Metal',
0122       10   => 'New Age',
0123       11   => 'Oldies',
0124       12   => 'Other',
0125       13   => 'Pop',
0126       14   => 'R&B',
0127       15   => 'Rap',
0128       16   => 'Reggae',
0129       17   => 'Rock',
0130       18   => 'Techno',
0131       19   => 'Industrial',
0132       20   => 'Alternative',
0133       21   => 'Ska',
0134       22   => 'Death Metal',
0135       23   => 'Pranks',
0136       24   => 'Soundtrack',
0137       25   => 'Euro-Techno',
0138       26   => 'Ambient',
0139       27   => 'Trip-Hop',
0140       28   => 'Vocal',
0141       29   => 'Jazz+Funk',
0142       30   => 'Fusion',
0143       31   => 'Trance',
0144       32   => 'Classical',
0145       33   => 'Instrumental',
0146       34   => 'Acid',
0147       35   => 'House',
0148       36   => 'Game',
0149       37   => 'Sound Clip',
0150       38   => 'Gospel',
0151       39   => 'Noise',
0152       40   => 'Alt. Rock',
0153       41   => 'Bass',
0154       42   => 'Soul',
0155       43   => 'Punk',
0156       44   => 'Space',
0157       45   => 'Meditative',
0158       46   => 'Instrumental Pop',
0159       47   => 'Instrumental Rock',
0160       48   => 'Ethnic',
0161       49   => 'Gothic',
0162       50   => 'Darkwave',
0163       51   => 'Techno-Industrial',
0164       52   => 'Electronic',
0165       53   => 'Pop-Folk',
0166       54   => 'Eurodance',
0167       55   => 'Dream',
0168       56   => 'Southern Rock',
0169       57   => 'Comedy',
0170       58   => 'Cult',
0171       59   => 'Gangsta Rap',
0172       60   => 'Top 40',
0173       61   => 'Christian Rap',
0174       62   => 'Pop/Funk',
0175       63   => 'Jungle',
0176       64   => 'Native American',
0177       65   => 'Cabaret',
0178       66   => 'New Wave',
0179       67   => 'Psychedelic',
0180       68   => 'Rave',
0181       69   => 'Showtunes',
0182       70   => 'Trailer',
0183       71   => 'Lo-Fi',
0184       72   => 'Tribal',
0185       73   => 'Acid Punk',
0186       74   => 'Acid Jazz',
0187       75   => 'Polka',
0188       76   => 'Retro',
0189       77   => 'Musical',
0190       78   => 'Rock & Roll',
0191       79   => 'Hard Rock',
0192       80   => 'Folk',
0193       81   => 'Folk/Rock',
0194       82   => 'National Folk',
0195       83   => 'Swing',
0196       84   => 'Fast-Fusion',
0197       85   => 'Bebob',
0198       86   => 'Latin',
0199       87   => 'Revival',
0200       88   => 'Celtic',
0201       89   => 'Bluegrass',
0202       90   => 'Avantgarde',
0203       91   => 'Gothic Rock',
0204       92   => 'Progressive Rock',
0205       93   => 'Psychedelic Rock',
0206       94   => 'Symphonic Rock',
0207       95   => 'Slow Rock',
0208       96   => 'Big Band',
0209       97   => 'Chorus',
0210       98   => 'Easy Listening',
0211       99   => 'Acoustic',
0212       100  => 'Humour',
0213       101  => 'Speech',
0214       102  => 'Chanson',
0215       103  => 'Opera',
0216       104  => 'Chamber Music',
0217       105  => 'Sonata',
0218       106  => 'Symphony',
0219       107  => 'Booty Bass',
0220       108  => 'Primus',
0221       109  => 'Porn Groove',
0222       110  => 'Satire',
0223       111  => 'Slow Jam',
0224       112  => 'Club',
0225       113  => 'Tango',
0226       114  => 'Samba',
0227       115  => 'Folklore',
0228       116  => 'Ballad',
0229       117  => 'Power Ballad',
0230       118  => 'Rhythmic Soul',
0231       119  => 'Freestyle',
0232       120  => 'Duet',
0233       121  => 'Punk Rock',
0234       122  => 'Drum Solo',
0235       123  => 'A Cappella',
0236       124  => 'Euro-House',
0237       125  => 'Dance Hall',
0238       126  => 'Goa',
0239       127  => 'Drum & Bass',
0240       128  => 'Club-House',
0241       129  => 'Hardcore',
0242       130  => 'Terror',
0243       131  => 'Indie',
0244       132  => 'BritPop',
0245       133  => 'Negerpunk',
0246       134  => 'Polsk Punk',
0247       135  => 'Beat',
0248       136  => 'Christian Gangsta Rap',
0249       137  => 'Heavy Metal',
0250       138  => 'Black Metal',
0251       139  => 'Crossover',
0252       140  => 'Contemporary Christian',
0253       141  => 'Christian Rock',
0254       142  => 'Merengue',
0255       143  => 'Salsa',
0256       144  => 'Thrash Metal',
0257       145  => 'Anime',
0258       146  => 'JPop',
0259       147  => 'Synthpop',
0260 
0261       255  => 'Unknown',
0262 
0263       'CR' => 'Cover',
0264       'RX' => 'Remix'
0265     );
0266 
0267     static $GenreLookupSCMPX = array();
0268     if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
0269       $GenreLookupSCMPX = $GenreLookup;
0270       // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
0271       // Extended ID3v1 genres invented by SCMPX
0272       // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
0273       $GenreLookupSCMPX[240] = 'Sacred';
0274       $GenreLookupSCMPX[241] = 'Northern Europe';
0275       $GenreLookupSCMPX[242] = 'Irish & Scottish';
0276       $GenreLookupSCMPX[243] = 'Scotland';
0277       $GenreLookupSCMPX[244] = 'Ethnic Europe';
0278       $GenreLookupSCMPX[245] = 'Enka';
0279       $GenreLookupSCMPX[246] = 'Children\'s Song';
0280       $GenreLookupSCMPX[247] = 'Japanese Sky';
0281       $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
0282       $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
0283       $GenreLookupSCMPX[250] = 'Japanese J-POP';
0284       $GenreLookupSCMPX[251] = 'Japanese Seiyu';
0285       $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
0286       $GenreLookupSCMPX[253] = 'Japanese Moemoe';
0287       $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
0288       //$GenreLookupSCMPX[255] = 'Japanese Anime';
0289     }
0290 
0291     return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
0292   }
0293 
0294   public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
0295     switch ($genreid) {
0296       case 'RX':
0297       case 'CR':
0298         break;
0299       default:
0300         if (!is_numeric($genreid)) {
0301           return false;
0302         }
0303         $genreid = intval($genreid); // to handle 3 or '3' or '03'
0304         break;
0305     }
0306     $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
0307     return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
0308   }
0309 
0310   public static function LookupGenreID($genre, $allowSCMPXextended=false) {
0311     $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
0312     $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
0313     foreach ($GenreLookup as $key => $value) {
0314       if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
0315         return $key;
0316       }
0317     }
0318     return false;
0319   }
0320 
0321   public static function StandardiseID3v1GenreName($OriginalGenre) {
0322     if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
0323       return self::LookupGenreName($GenreID);
0324     }
0325     return $OriginalGenre;
0326   }
0327 
0328   public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
0329     $ID3v1Tag  = 'TAG';
0330     $ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
0331     $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
0332     $ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
0333     $ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
0334     if (!empty($track) && ($track > 0) && ($track <= 255)) {
0335       $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
0336       $ID3v1Tag .= "\x00";
0337       if (gettype($track) == 'string') {
0338         $track = (int) $track;
0339       }
0340       $ID3v1Tag .= chr($track);
0341     } else {
0342       $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
0343     }
0344     if (($genreid < 0) || ($genreid > 147)) {
0345       $genreid = 255; // 'unknown' genre
0346     }
0347     switch (gettype($genreid)) {
0348       case 'string':
0349       case 'integer':
0350         $ID3v1Tag .= chr(intval($genreid));
0351         break;
0352       default:
0353         $ID3v1Tag .= chr(255); // 'unknown' genre
0354         break;
0355     }
0356 
0357     return $ID3v1Tag;
0358   }
0359 
0360 }