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 }