rlm@3: | rlm@3: // | Allan Hansen | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // | module.tag.id3v1.php | rlm@3: // | module for analyzing ID3v1 tags | rlm@3: // | dependencies: NONE | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // rlm@3: // $Id: module.tag.id3v1.php,v 1.6 2006/11/16 16:19:52 ah Exp $ rlm@3: rlm@3: rlm@3: rlm@3: class getid3_id3v1 extends getid3_handler rlm@3: { rlm@3: rlm@3: public function Analyze() { rlm@3: rlm@3: $getid3 = $this->getid3; rlm@3: rlm@3: fseek($getid3->fp, -256, SEEK_END); rlm@3: $pre_id3v1 = fread($getid3->fp, 128); rlm@3: $id3v1_tag = fread($getid3->fp, 128); rlm@3: rlm@3: if (substr($id3v1_tag, 0, 3) == 'TAG') { rlm@3: rlm@3: $getid3->info['avdataend'] -= 128; rlm@3: rlm@3: // Shortcut rlm@3: $getid3->info['id3v1'] = array (); rlm@3: $info_id3v1 = &$getid3->info['id3v1']; rlm@3: rlm@3: $info_id3v1['title'] = getid3_id3v1::cutfield(substr($id3v1_tag, 3, 30)); rlm@3: $info_id3v1['artist'] = getid3_id3v1::cutfield(substr($id3v1_tag, 33, 30)); rlm@3: $info_id3v1['album'] = getid3_id3v1::cutfield(substr($id3v1_tag, 63, 30)); rlm@3: $info_id3v1['year'] = getid3_id3v1::cutfield(substr($id3v1_tag, 93, 4)); rlm@3: $info_id3v1['comment'] = substr($id3v1_tag, 97, 30); // can't remove nulls yet, track detection depends on them rlm@3: $info_id3v1['genreid'] = ord(substr($id3v1_tag, 127, 1)); rlm@3: rlm@3: // If second-last byte of comment field is null and last byte of comment field is non-null then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number rlm@3: if (($id3v1_tag{125} === "\x00") && ($id3v1_tag{126} !== "\x00")) { rlm@3: $info_id3v1['track'] = ord(substr($info_id3v1['comment'], 29, 1)); rlm@3: $info_id3v1['comment'] = substr($info_id3v1['comment'], 0, 28); rlm@3: } rlm@3: $info_id3v1['comment'] = getid3_id3v1::cutfield($info_id3v1['comment']); rlm@3: rlm@3: $info_id3v1['genre'] = getid3_id3v1::LookupGenreName($info_id3v1['genreid']); rlm@3: if (!empty($info_id3v1['genre'])) { rlm@3: unset($info_id3v1['genreid']); rlm@3: } rlm@3: if (empty($info_id3v1['genre']) || (@$info_id3v1['genre'] == 'Unknown')) { rlm@3: unset($info_id3v1['genre']); rlm@3: } rlm@3: rlm@3: foreach ($info_id3v1 as $key => $value) { rlm@3: $key != 'comments' and $info_id3v1['comments'][$key][0] = $value; rlm@3: } rlm@3: rlm@3: $info_id3v1['tag_offset_end'] = filesize($getid3->filename); rlm@3: $info_id3v1['tag_offset_start'] = $info_id3v1['tag_offset_end'] - 128; rlm@3: } rlm@3: rlm@3: if (substr($pre_id3v1, 0, 3) == 'TAG') { rlm@3: // The way iTunes handles tags is, well, brain-damaged. rlm@3: // It completely ignores v1 if ID3v2 is present. rlm@3: // This goes as far as adding a new v1 tag *even if there already is one* rlm@3: rlm@3: // A suspected double-ID3v1 tag has been detected, but it could be that the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag rlm@3: if (substr($pre_id3v1, 96, 8) == 'APETAGEX') { rlm@3: // an APE tag footer was found before the last ID3v1, assume false "TAG" synch rlm@3: } elseif (substr($pre_id3v1, 119, 6) == 'LYRICS') { rlm@3: // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch rlm@3: } else { rlm@3: // APE and Lyrics3 footers not found - assume double ID3v1 rlm@3: $getid3->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes.'); rlm@3: $getid3->info['avdataend'] -= 128; rlm@3: } rlm@3: } rlm@3: rlm@3: return true; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function cutfield($str) { rlm@3: rlm@3: return trim(substr($str, 0, strcspn($str, "\x00"))); rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function ArrayOfGenres($allow_SCMPX_extended=false) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 0 => 'Blues', rlm@3: 1 => 'Classic Rock', rlm@3: 2 => 'Country', rlm@3: 3 => 'Dance', rlm@3: 4 => 'Disco', rlm@3: 5 => 'Funk', rlm@3: 6 => 'Grunge', rlm@3: 7 => 'Hip-Hop', rlm@3: 8 => 'Jazz', rlm@3: 9 => 'Metal', rlm@3: 10 => 'New Age', rlm@3: 11 => 'Oldies', rlm@3: 12 => 'Other', rlm@3: 13 => 'Pop', rlm@3: 14 => 'R&B', rlm@3: 15 => 'Rap', rlm@3: 16 => 'Reggae', rlm@3: 17 => 'Rock', rlm@3: 18 => 'Techno', rlm@3: 19 => 'Industrial', rlm@3: 20 => 'Alternative', rlm@3: 21 => 'Ska', rlm@3: 22 => 'Death Metal', rlm@3: 23 => 'Pranks', rlm@3: 24 => 'Soundtrack', rlm@3: 25 => 'Euro-Techno', rlm@3: 26 => 'Ambient', rlm@3: 27 => 'Trip-Hop', rlm@3: 28 => 'Vocal', rlm@3: 29 => 'Jazz+Funk', rlm@3: 30 => 'Fusion', rlm@3: 31 => 'Trance', rlm@3: 32 => 'Classical', rlm@3: 33 => 'Instrumental', rlm@3: 34 => 'Acid', rlm@3: 35 => 'House', rlm@3: 36 => 'Game', rlm@3: 37 => 'Sound Clip', rlm@3: 38 => 'Gospel', rlm@3: 39 => 'Noise', rlm@3: 40 => 'Alt. Rock', rlm@3: 41 => 'Bass', rlm@3: 42 => 'Soul', rlm@3: 43 => 'Punk', rlm@3: 44 => 'Space', rlm@3: 45 => 'Meditative', rlm@3: 46 => 'Instrumental Pop', rlm@3: 47 => 'Instrumental Rock', rlm@3: 48 => 'Ethnic', rlm@3: 49 => 'Gothic', rlm@3: 50 => 'Darkwave', rlm@3: 51 => 'Techno-Industrial', rlm@3: 52 => 'Electronic', rlm@3: 53 => 'Pop-Folk', rlm@3: 54 => 'Eurodance', rlm@3: 55 => 'Dream', rlm@3: 56 => 'Southern Rock', rlm@3: 57 => 'Comedy', rlm@3: 58 => 'Cult', rlm@3: 59 => 'Gangsta Rap', rlm@3: 60 => 'Top 40', rlm@3: 61 => 'Christian Rap', rlm@3: 62 => 'Pop/Funk', rlm@3: 63 => 'Jungle', rlm@3: 64 => 'Native American', rlm@3: 65 => 'Cabaret', rlm@3: 66 => 'New Wave', rlm@3: 67 => 'Psychedelic', rlm@3: 68 => 'Rave', rlm@3: 69 => 'Showtunes', rlm@3: 70 => 'Trailer', rlm@3: 71 => 'Lo-Fi', rlm@3: 72 => 'Tribal', rlm@3: 73 => 'Acid Punk', rlm@3: 74 => 'Acid Jazz', rlm@3: 75 => 'Polka', rlm@3: 76 => 'Retro', rlm@3: 77 => 'Musical', rlm@3: 78 => 'Rock & Roll', rlm@3: 79 => 'Hard Rock', rlm@3: 80 => 'Folk', rlm@3: 81 => 'Folk/Rock', rlm@3: 82 => 'National Folk', rlm@3: 83 => 'Swing', rlm@3: 84 => 'Fast-Fusion', rlm@3: 85 => 'Bebob', rlm@3: 86 => 'Latin', rlm@3: 87 => 'Revival', rlm@3: 88 => 'Celtic', rlm@3: 89 => 'Bluegrass', rlm@3: 90 => 'Avantgarde', rlm@3: 91 => 'Gothic Rock', rlm@3: 92 => 'Progressive Rock', rlm@3: 93 => 'Psychedelic Rock', rlm@3: 94 => 'Symphonic Rock', rlm@3: 95 => 'Slow Rock', rlm@3: 96 => 'Big Band', rlm@3: 97 => 'Chorus', rlm@3: 98 => 'Easy Listening', rlm@3: 99 => 'Acoustic', rlm@3: 100 => 'Humour', rlm@3: 101 => 'Speech', rlm@3: 102 => 'Chanson', rlm@3: 103 => 'Opera', rlm@3: 104 => 'Chamber Music', rlm@3: 105 => 'Sonata', rlm@3: 106 => 'Symphony', rlm@3: 107 => 'Booty Bass', rlm@3: 108 => 'Primus', rlm@3: 109 => 'Porn Groove', rlm@3: 110 => 'Satire', rlm@3: 111 => 'Slow Jam', rlm@3: 112 => 'Club', rlm@3: 113 => 'Tango', rlm@3: 114 => 'Samba', rlm@3: 115 => 'Folklore', rlm@3: 116 => 'Ballad', rlm@3: 117 => 'Power Ballad', rlm@3: 118 => 'Rhythmic Soul', rlm@3: 119 => 'Freestyle', rlm@3: 120 => 'Duet', rlm@3: 121 => 'Punk Rock', rlm@3: 122 => 'Drum Solo', rlm@3: 123 => 'A Cappella', rlm@3: 124 => 'Euro-House', rlm@3: 125 => 'Dance Hall', rlm@3: 126 => 'Goa', rlm@3: 127 => 'Drum & Bass', rlm@3: 128 => 'Club-House', rlm@3: 129 => 'Hardcore', rlm@3: 130 => 'Terror', rlm@3: 131 => 'Indie', rlm@3: 132 => 'BritPop', rlm@3: 133 => 'Negerpunk', rlm@3: 134 => 'Polsk Punk', rlm@3: 135 => 'Beat', rlm@3: 136 => 'Christian Gangsta Rap', rlm@3: 137 => 'Heavy Metal', rlm@3: 138 => 'Black Metal', rlm@3: 139 => 'Crossover', rlm@3: 140 => 'Contemporary Christian', rlm@3: 141 => 'Christian Rock', rlm@3: 142 => 'Merengue', rlm@3: 143 => 'Salsa', rlm@3: 144 => 'Trash Metal', rlm@3: 145 => 'Anime', rlm@3: 146 => 'JPop', rlm@3: 147 => 'Synthpop', rlm@3: rlm@3: 255 => 'Unknown', rlm@3: rlm@3: 'CR' => 'Cover', rlm@3: 'RX' => 'Remix' rlm@3: ); rlm@3: rlm@3: static $lookupSCMPX = array (); rlm@3: if ($allow_SCMPX_extended && empty($lookupSCMPX)) { rlm@3: $lookupSCMPX = $lookup; rlm@3: // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended rlm@3: // Extended ID3v1 genres invented by SCMPX rlm@3: // Note that 255 "Japanese Anime" conflicts with standard "Unknown" rlm@3: $lookupSCMPX[240] = 'Sacred'; rlm@3: $lookupSCMPX[241] = 'Northern Europe'; rlm@3: $lookupSCMPX[242] = 'Irish & Scottish'; rlm@3: $lookupSCMPX[243] = 'Scotland'; rlm@3: $lookupSCMPX[244] = 'Ethnic Europe'; rlm@3: $lookupSCMPX[245] = 'Enka'; rlm@3: $lookupSCMPX[246] = 'Children\'s Song'; rlm@3: $lookupSCMPX[247] = 'Japanese Sky'; rlm@3: $lookupSCMPX[248] = 'Japanese Heavy Rock'; rlm@3: $lookupSCMPX[249] = 'Japanese Doom Rock'; rlm@3: $lookupSCMPX[250] = 'Japanese J-POP'; rlm@3: $lookupSCMPX[251] = 'Japanese Seiyu'; rlm@3: $lookupSCMPX[252] = 'Japanese Ambient Techno'; rlm@3: $lookupSCMPX[253] = 'Japanese Moemoe'; rlm@3: $lookupSCMPX[254] = 'Japanese Tokusatsu'; rlm@3: //$lookupSCMPX[255] = 'Japanese Anime'; rlm@3: } rlm@3: rlm@3: return ($allow_SCMPX_extended ? $lookupSCMPX : $lookup); rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function LookupGenreName($genre_id, $allow_SCMPX_extended=true) { rlm@3: rlm@3: switch ($genre_id) { rlm@3: case 'RX': rlm@3: case 'CR': rlm@3: break; rlm@3: default: rlm@3: $genre_id = intval($genre_id); // to handle 3 or '3' or '03' rlm@3: break; rlm@3: } rlm@3: $lookup = getid3_id3v1::ArrayOfGenres($allow_SCMPX_extended); rlm@3: return (isset($lookup[$genre_id]) ? $lookup[$genre_id] : false); rlm@3: } rlm@3: rlm@3: rlm@3: public static function LookupGenreID($genre, $allow_SCMPX_extended=false) { rlm@3: rlm@3: $lookup = getid3_id3v1::ArrayOfGenres($allow_SCMPX_extended); rlm@3: $lower_case_no_space_search_term = strtolower(str_replace(' ', '', $genre)); rlm@3: foreach ($lookup as $key => $value) { rlm@3: foreach ($lookup as $key => $value) { rlm@3: if (strtolower(str_replace(' ', '', $value)) == $lower_case_no_space_search_term) { rlm@3: return $key; rlm@3: } rlm@3: } rlm@3: return false; rlm@3: } rlm@3: return (isset($lookup[$genre_id]) ? $lookup[$genre_id] : false); rlm@3: } rlm@3: rlm@3: } rlm@3: rlm@3: rlm@3: ?>