1: <?php
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18: class getid3_id3v1 extends getid3_handler
19: {
20:
21: public function Analyze() {
22: $info = &$this->getid3->info;
23:
24: if (!getid3_lib::intValueSupported($info['filesize'])) {
25: $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
26: return false;
27: }
28:
29: $this->fseek(-256, SEEK_END);
30: $preid3v1 = $this->fread(128);
31: $id3v1tag = $this->fread(128);
32:
33: if (substr($id3v1tag, 0, 3) == 'TAG') {
34:
35: $info['avdataend'] = $info['filesize'] - 128;
36:
37: $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
38: $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
39: $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
40: $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
41: $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30);
42: $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
43:
44:
45:
46: if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
47: $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
48: $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
49: }
50: $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
51:
52: $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
53: if (!empty($ParsedID3v1['genre'])) {
54: unset($ParsedID3v1['genreid']);
55: }
56: if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
57: unset($ParsedID3v1['genre']);
58: }
59:
60: foreach ($ParsedID3v1 as $key => $value) {
61: $ParsedID3v1['comments'][$key][0] = $value;
62: }
63:
64:
65: $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
66: $ParsedID3v1['title'],
67: $ParsedID3v1['artist'],
68: $ParsedID3v1['album'],
69: $ParsedID3v1['year'],
70: (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
71: $ParsedID3v1['comment'],
72: (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
73: $ParsedID3v1['padding_valid'] = true;
74: if ($id3v1tag !== $GoodFormatID3v1tag) {
75: $ParsedID3v1['padding_valid'] = false;
76: $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
77: }
78:
79: $ParsedID3v1['tag_offset_end'] = $info['filesize'];
80: $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
81:
82: $info['id3v1'] = $ParsedID3v1;
83: }
84:
85: if (substr($preid3v1, 0, 3) == 'TAG') {
86:
87:
88:
89:
90:
91:
92: if (substr($preid3v1, 96, 8) == 'APETAGEX') {
93:
94: } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
95:
96: } else {
97:
98: $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
99: $info['avdataend'] -= 128;
100: }
101: }
102:
103: return true;
104: }
105:
106: public static function cutfield($str) {
107: return trim(substr($str, 0, strcspn($str, "\x00")));
108: }
109:
110: public static function ArrayOfGenres($allowSCMPXextended=false) {
111: static $GenreLookup = array(
112: 0 => 'Blues',
113: 1 => 'Classic Rock',
114: 2 => 'Country',
115: 3 => 'Dance',
116: 4 => 'Disco',
117: 5 => 'Funk',
118: 6 => 'Grunge',
119: 7 => 'Hip-Hop',
120: 8 => 'Jazz',
121: 9 => 'Metal',
122: 10 => 'New Age',
123: 11 => 'Oldies',
124: 12 => 'Other',
125: 13 => 'Pop',
126: 14 => 'R&B',
127: 15 => 'Rap',
128: 16 => 'Reggae',
129: 17 => 'Rock',
130: 18 => 'Techno',
131: 19 => 'Industrial',
132: 20 => 'Alternative',
133: 21 => 'Ska',
134: 22 => 'Death Metal',
135: 23 => 'Pranks',
136: 24 => 'Soundtrack',
137: 25 => 'Euro-Techno',
138: 26 => 'Ambient',
139: 27 => 'Trip-Hop',
140: 28 => 'Vocal',
141: 29 => 'Jazz+Funk',
142: 30 => 'Fusion',
143: 31 => 'Trance',
144: 32 => 'Classical',
145: 33 => 'Instrumental',
146: 34 => 'Acid',
147: 35 => 'House',
148: 36 => 'Game',
149: 37 => 'Sound Clip',
150: 38 => 'Gospel',
151: 39 => 'Noise',
152: 40 => 'Alt. Rock',
153: 41 => 'Bass',
154: 42 => 'Soul',
155: 43 => 'Punk',
156: 44 => 'Space',
157: 45 => 'Meditative',
158: 46 => 'Instrumental Pop',
159: 47 => 'Instrumental Rock',
160: 48 => 'Ethnic',
161: 49 => 'Gothic',
162: 50 => 'Darkwave',
163: 51 => 'Techno-Industrial',
164: 52 => 'Electronic',
165: 53 => 'Pop-Folk',
166: 54 => 'Eurodance',
167: 55 => 'Dream',
168: 56 => 'Southern Rock',
169: 57 => 'Comedy',
170: 58 => 'Cult',
171: 59 => 'Gangsta Rap',
172: 60 => 'Top 40',
173: 61 => 'Christian Rap',
174: 62 => 'Pop/Funk',
175: 63 => 'Jungle',
176: 64 => 'Native American',
177: 65 => 'Cabaret',
178: 66 => 'New Wave',
179: 67 => 'Psychedelic',
180: 68 => 'Rave',
181: 69 => 'Showtunes',
182: 70 => 'Trailer',
183: 71 => 'Lo-Fi',
184: 72 => 'Tribal',
185: 73 => 'Acid Punk',
186: 74 => 'Acid Jazz',
187: 75 => 'Polka',
188: 76 => 'Retro',
189: 77 => 'Musical',
190: 78 => 'Rock & Roll',
191: 79 => 'Hard Rock',
192: 80 => 'Folk',
193: 81 => 'Folk/Rock',
194: 82 => 'National Folk',
195: 83 => 'Swing',
196: 84 => 'Fast-Fusion',
197: 85 => 'Bebob',
198: 86 => 'Latin',
199: 87 => 'Revival',
200: 88 => 'Celtic',
201: 89 => 'Bluegrass',
202: 90 => 'Avantgarde',
203: 91 => 'Gothic Rock',
204: 92 => 'Progressive Rock',
205: 93 => 'Psychedelic Rock',
206: 94 => 'Symphonic Rock',
207: 95 => 'Slow Rock',
208: 96 => 'Big Band',
209: 97 => 'Chorus',
210: 98 => 'Easy Listening',
211: 99 => 'Acoustic',
212: 100 => 'Humour',
213: 101 => 'Speech',
214: 102 => 'Chanson',
215: 103 => 'Opera',
216: 104 => 'Chamber Music',
217: 105 => 'Sonata',
218: 106 => 'Symphony',
219: 107 => 'Booty Bass',
220: 108 => 'Primus',
221: 109 => 'Porn Groove',
222: 110 => 'Satire',
223: 111 => 'Slow Jam',
224: 112 => 'Club',
225: 113 => 'Tango',
226: 114 => 'Samba',
227: 115 => 'Folklore',
228: 116 => 'Ballad',
229: 117 => 'Power Ballad',
230: 118 => 'Rhythmic Soul',
231: 119 => 'Freestyle',
232: 120 => 'Duet',
233: 121 => 'Punk Rock',
234: 122 => 'Drum Solo',
235: 123 => 'A Cappella',
236: 124 => 'Euro-House',
237: 125 => 'Dance Hall',
238: 126 => 'Goa',
239: 127 => 'Drum & Bass',
240: 128 => 'Club-House',
241: 129 => 'Hardcore',
242: 130 => 'Terror',
243: 131 => 'Indie',
244: 132 => 'BritPop',
245: 133 => 'Negerpunk',
246: 134 => 'Polsk Punk',
247: 135 => 'Beat',
248: 136 => 'Christian Gangsta Rap',
249: 137 => 'Heavy Metal',
250: 138 => 'Black Metal',
251: 139 => 'Crossover',
252: 140 => 'Contemporary Christian',
253: 141 => 'Christian Rock',
254: 142 => 'Merengue',
255: 143 => 'Salsa',
256: 144 => 'Thrash Metal',
257: 145 => 'Anime',
258: 146 => 'JPop',
259: 147 => 'Synthpop',
260:
261: 255 => 'Unknown',
262:
263: 'CR' => 'Cover',
264: 'RX' => 'Remix'
265: );
266:
267: static $GenreLookupSCMPX = array();
268: if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
269: $GenreLookupSCMPX = $GenreLookup;
270:
271:
272:
273: $GenreLookupSCMPX[240] = 'Sacred';
274: $GenreLookupSCMPX[241] = 'Northern Europe';
275: $GenreLookupSCMPX[242] = 'Irish & Scottish';
276: $GenreLookupSCMPX[243] = 'Scotland';
277: $GenreLookupSCMPX[244] = 'Ethnic Europe';
278: $GenreLookupSCMPX[245] = 'Enka';
279: $GenreLookupSCMPX[246] = 'Children\'s Song';
280: $GenreLookupSCMPX[247] = 'Japanese Sky';
281: $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
282: $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
283: $GenreLookupSCMPX[250] = 'Japanese J-POP';
284: $GenreLookupSCMPX[251] = 'Japanese Seiyu';
285: $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
286: $GenreLookupSCMPX[253] = 'Japanese Moemoe';
287: $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
288:
289: }
290:
291: return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
292: }
293:
294: public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
295: switch ($genreid) {
296: case 'RX':
297: case 'CR':
298: break;
299: default:
300: if (!is_numeric($genreid)) {
301: return false;
302: }
303: $genreid = intval($genreid);
304: break;
305: }
306: $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
307: return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
308: }
309:
310: public static function LookupGenreID($genre, $allowSCMPXextended=false) {
311: $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
312: $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
313: foreach ($GenreLookup as $key => $value) {
314: if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
315: return $key;
316: }
317: }
318: return false;
319: }
320:
321: public static function StandardiseID3v1GenreName($OriginalGenre) {
322: if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
323: return self::LookupGenreName($GenreID);
324: }
325: return $OriginalGenre;
326: }
327:
328: public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
329: $ID3v1Tag = 'TAG';
330: $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
331: $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
332: $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
333: $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
334: if (!empty($track) && ($track > 0) && ($track <= 255)) {
335: $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
336: $ID3v1Tag .= "\x00";
337: if (gettype($track) == 'string') {
338: $track = (int) $track;
339: }
340: $ID3v1Tag .= chr($track);
341: } else {
342: $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
343: }
344: if (($genreid < 0) || ($genreid > 147)) {
345: $genreid = 255;
346: }
347: switch (gettype($genreid)) {
348: case 'string':
349: case 'integer':
350: $ID3v1Tag .= chr(intval($genreid));
351: break;
352: default:
353: $ID3v1Tag .= chr(255);
354: break;
355: }
356:
357: return $ID3v1Tag;
358: }
359:
360: }
361: