rlm@3
|
1 <?php
|
rlm@3
|
2 // +----------------------------------------------------------------------+
|
rlm@3
|
3 // | PHP version 5 |
|
rlm@3
|
4 // +----------------------------------------------------------------------+
|
rlm@3
|
5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen |
|
rlm@3
|
6 // +----------------------------------------------------------------------+
|
rlm@3
|
7 // | This source file is subject to version 2 of the GPL license, |
|
rlm@3
|
8 // | that is bundled with this package in the file license.txt and is |
|
rlm@3
|
9 // | available through the world-wide-web at the following url: |
|
rlm@3
|
10 // | http://www.gnu.org/copyleft/gpl.html |
|
rlm@3
|
11 // +----------------------------------------------------------------------+
|
rlm@3
|
12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |
|
rlm@3
|
13 // +----------------------------------------------------------------------+
|
rlm@3
|
14 // | Authors: James Heinrich <infoØgetid3*org> |
|
rlm@3
|
15 // | Allan Hansen <ahØartemis*dk> |
|
rlm@3
|
16 // +----------------------------------------------------------------------+
|
rlm@3
|
17 // | module.tag.apetag.php |
|
rlm@3
|
18 // | module for analyzing APE tags |
|
rlm@3
|
19 // | dependencies: NONE |
|
rlm@3
|
20 // +----------------------------------------------------------------------+
|
rlm@3
|
21 //
|
rlm@3
|
22 // $Id: module.tag.apetag.php,v 1.5 2006/11/16 14:05:21 ah Exp $
|
rlm@3
|
23
|
rlm@3
|
24
|
rlm@3
|
25
|
rlm@3
|
26 class getid3_apetag extends getid3_handler
|
rlm@3
|
27 {
|
rlm@3
|
28 /*
|
rlm@3
|
29 ID3v1_TAG_SIZE = 128;
|
rlm@3
|
30 APETAG_HEADER_SIZE = 32;
|
rlm@3
|
31 LYRICS3_TAG_SIZE = 10;
|
rlm@3
|
32 */
|
rlm@3
|
33
|
rlm@3
|
34 public $option_override_end_offset = 0;
|
rlm@3
|
35
|
rlm@3
|
36
|
rlm@3
|
37
|
rlm@3
|
38 public function Analyze() {
|
rlm@3
|
39
|
rlm@3
|
40 $getid3 = $this->getid3;
|
rlm@3
|
41
|
rlm@3
|
42 if ($this->option_override_end_offset == 0) {
|
rlm@3
|
43
|
rlm@3
|
44 fseek($getid3->fp, 0 - 170, SEEK_END); // 170 = ID3v1_TAG_SIZE + APETAG_HEADER_SIZE + LYRICS3_TAG_SIZE
|
rlm@3
|
45 $apetag_footer_id3v1 = fread($getid3->fp, 170); // 170 = ID3v1_TAG_SIZE + APETAG_HEADER_SIZE + LYRICS3_TAG_SIZE
|
rlm@3
|
46
|
rlm@3
|
47 // APE tag found before ID3v1
|
rlm@3
|
48 if (substr($apetag_footer_id3v1, strlen($apetag_footer_id3v1) - 160, 8) == 'APETAGEX') { // 160 = ID3v1_TAG_SIZE + APETAG_HEADER_SIZE
|
rlm@3
|
49 $getid3->info['ape']['tag_offset_end'] = filesize($getid3->filename) - 128; // 128 = ID3v1_TAG_SIZE
|
rlm@3
|
50 }
|
rlm@3
|
51
|
rlm@3
|
52 // APE tag found, no ID3v1
|
rlm@3
|
53 elseif (substr($apetag_footer_id3v1, strlen($apetag_footer_id3v1) - 32, 8) == 'APETAGEX') { // 32 = APETAG_HEADER_SIZE
|
rlm@3
|
54 $getid3->info['ape']['tag_offset_end'] = filesize($getid3->filename);
|
rlm@3
|
55 }
|
rlm@3
|
56
|
rlm@3
|
57 }
|
rlm@3
|
58 else {
|
rlm@3
|
59
|
rlm@3
|
60 fseek($getid3->fp, $this->option_override_end_offset - 32, SEEK_SET); // 32 = APETAG_HEADER_SIZE
|
rlm@3
|
61 if (fread($getid3->fp, 8) == 'APETAGEX') {
|
rlm@3
|
62 $getid3->info['ape']['tag_offset_end'] = $this->option_override_end_offset;
|
rlm@3
|
63 }
|
rlm@3
|
64
|
rlm@3
|
65 }
|
rlm@3
|
66
|
rlm@3
|
67 // APE tag not found
|
rlm@3
|
68 if (!@$getid3->info['ape']['tag_offset_end']) {
|
rlm@3
|
69 return false;
|
rlm@3
|
70 }
|
rlm@3
|
71
|
rlm@3
|
72 // Shortcut
|
rlm@3
|
73 $info_ape = &$getid3->info['ape'];
|
rlm@3
|
74
|
rlm@3
|
75 // Read and parse footer
|
rlm@3
|
76 fseek($getid3->fp, $info_ape['tag_offset_end'] - 32, SEEK_SET); // 32 = APETAG_HEADER_SIZE
|
rlm@3
|
77 $apetag_footer_data = fread($getid3->fp, 32);
|
rlm@3
|
78 if (!($this->ParseAPEHeaderFooter($apetag_footer_data, $info_ape['footer']))) {
|
rlm@3
|
79 throw new getid3_exception('Error parsing APE footer at offset '.$info_ape['tag_offset_end']);
|
rlm@3
|
80 }
|
rlm@3
|
81
|
rlm@3
|
82 if (isset($info_ape['footer']['flags']['header']) && $info_ape['footer']['flags']['header']) {
|
rlm@3
|
83 fseek($getid3->fp, $info_ape['tag_offset_end'] - $info_ape['footer']['raw']['tagsize'] - 32, SEEK_SET);
|
rlm@3
|
84 $info_ape['tag_offset_start'] = ftell($getid3->fp);
|
rlm@3
|
85 $apetag_data = fread($getid3->fp, $info_ape['footer']['raw']['tagsize'] + 32);
|
rlm@3
|
86 }
|
rlm@3
|
87 else {
|
rlm@3
|
88 $info_ape['tag_offset_start'] = $info_ape['tag_offset_end'] - $info_ape['footer']['raw']['tagsize'];
|
rlm@3
|
89 fseek($getid3->fp, $info_ape['tag_offset_start'], SEEK_SET);
|
rlm@3
|
90 $apetag_data = fread($getid3->fp, $info_ape['footer']['raw']['tagsize']);
|
rlm@3
|
91 }
|
rlm@3
|
92 $getid3->info['avdataend'] = $info_ape['tag_offset_start'];
|
rlm@3
|
93
|
rlm@3
|
94 if (isset($getid3->info['id3v1']['tag_offset_start']) && ($getid3->info['id3v1']['tag_offset_start'] < $info_ape['tag_offset_end'])) {
|
rlm@3
|
95 $getid3->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
|
rlm@3
|
96 unset($getid3->info['id3v1']);
|
rlm@3
|
97 }
|
rlm@3
|
98
|
rlm@3
|
99 $offset = 0;
|
rlm@3
|
100 if (isset($info_ape['footer']['flags']['header']) && $info_ape['footer']['flags']['header']) {
|
rlm@3
|
101 if (!$this->ParseAPEHeaderFooter(substr($apetag_data, 0, 32), $info_ape['header'])) {
|
rlm@3
|
102 throw new getid3_exception('Error parsing APE header at offset '.$info_ape['tag_offset_start']);
|
rlm@3
|
103 }
|
rlm@3
|
104 $offset = 32;
|
rlm@3
|
105 }
|
rlm@3
|
106
|
rlm@3
|
107 // Shortcut
|
rlm@3
|
108 $getid3->info['replay_gain'] = array ();
|
rlm@3
|
109 $info_replaygain = &$getid3->info['replay_gain'];
|
rlm@3
|
110
|
rlm@3
|
111 for ($i = 0; $i < $info_ape['footer']['raw']['tag_items']; $i++) {
|
rlm@3
|
112 $value_size = getid3_lib::LittleEndian2Int(substr($apetag_data, $offset, 4));
|
rlm@3
|
113 $item_flags = getid3_lib::LittleEndian2Int(substr($apetag_data, $offset + 4, 4));
|
rlm@3
|
114 $offset += 8;
|
rlm@3
|
115
|
rlm@3
|
116 if (strstr(substr($apetag_data, $offset), "\x00") === false) {
|
rlm@3
|
117 throw new getid3_exception('Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts ' . $offset . ' bytes into the APE tag, at file offset '.($info_ape['tag_offset_start'] + $offset));
|
rlm@3
|
118 }
|
rlm@3
|
119
|
rlm@3
|
120 $item_key_length = strpos($apetag_data, "\x00", $offset) - $offset;
|
rlm@3
|
121 $item_key = strtolower(substr($apetag_data, $offset, $item_key_length));
|
rlm@3
|
122
|
rlm@3
|
123 // Shortcut
|
rlm@3
|
124 $info_ape['items'][$item_key] = array ();
|
rlm@3
|
125 $info_ape_items_current = &$info_ape['items'][$item_key];
|
rlm@3
|
126
|
rlm@3
|
127 $offset += $item_key_length + 1; // skip 0x00 terminator
|
rlm@3
|
128 $info_ape_items_current['data'] = substr($apetag_data, $offset, $value_size);
|
rlm@3
|
129 $offset += $value_size;
|
rlm@3
|
130
|
rlm@3
|
131
|
rlm@3
|
132 $info_ape_items_current['flags'] = $this->ParseAPEtagFlags($item_flags);
|
rlm@3
|
133
|
rlm@3
|
134 switch ($info_ape_items_current['flags']['item_contents_raw']) {
|
rlm@3
|
135 case 0: // UTF-8
|
rlm@3
|
136 case 3: // Locator (URL, filename, etc), UTF-8 encoded
|
rlm@3
|
137 $info_ape_items_current['data'] = explode("\x00", trim($info_ape_items_current['data']));
|
rlm@3
|
138 break;
|
rlm@3
|
139
|
rlm@3
|
140 default: // binary data
|
rlm@3
|
141 break;
|
rlm@3
|
142 }
|
rlm@3
|
143
|
rlm@3
|
144 switch (strtolower($item_key)) {
|
rlm@3
|
145 case 'replaygain_track_gain':
|
rlm@3
|
146 $info_replaygain['track']['adjustment'] = (float)str_replace(',', '.', $info_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
rlm@3
|
147 $info_replaygain['track']['originator'] = 'unspecified';
|
rlm@3
|
148 break;
|
rlm@3
|
149
|
rlm@3
|
150 case 'replaygain_track_peak':
|
rlm@3
|
151 $info_replaygain['track']['peak'] = (float)str_replace(',', '.', $info_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
rlm@3
|
152 $info_replaygain['track']['originator'] = 'unspecified';
|
rlm@3
|
153 if ($info_replaygain['track']['peak'] <= 0) {
|
rlm@3
|
154 $getid3->warning('ReplayGain Track peak from APEtag appears invalid: '.$info_replaygain['track']['peak'].' (original value = "'.$info_ape_items_current['data'][0].'")');
|
rlm@3
|
155 }
|
rlm@3
|
156 break;
|
rlm@3
|
157
|
rlm@3
|
158 case 'replaygain_album_gain':
|
rlm@3
|
159 $info_replaygain['album']['adjustment'] = (float)str_replace(',', '.', $info_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
rlm@3
|
160 $info_replaygain['album']['originator'] = 'unspecified';
|
rlm@3
|
161 break;
|
rlm@3
|
162
|
rlm@3
|
163 case 'replaygain_album_peak':
|
rlm@3
|
164 $info_replaygain['album']['peak'] = (float)str_replace(',', '.', $info_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
rlm@3
|
165 $info_replaygain['album']['originator'] = 'unspecified';
|
rlm@3
|
166 if ($info_replaygain['album']['peak'] <= 0) {
|
rlm@3
|
167 $getid3->warning('ReplayGain Album peak from APEtag appears invalid: '.$info_replaygain['album']['peak'].' (original value = "'.$info_ape_items_current['data'][0].'")');
|
rlm@3
|
168 }
|
rlm@3
|
169 break;
|
rlm@3
|
170
|
rlm@3
|
171 case 'mp3gain_undo':
|
rlm@3
|
172 list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $info_ape_items_current['data'][0]);
|
rlm@3
|
173 $info_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
|
rlm@3
|
174 $info_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
|
rlm@3
|
175 $info_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
|
rlm@3
|
176 break;
|
rlm@3
|
177
|
rlm@3
|
178 case 'mp3gain_minmax':
|
rlm@3
|
179 list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $info_ape_items_current['data'][0]);
|
rlm@3
|
180 $info_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
|
rlm@3
|
181 $info_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
|
rlm@3
|
182 break;
|
rlm@3
|
183
|
rlm@3
|
184 case 'mp3gain_album_minmax':
|
rlm@3
|
185 list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $info_ape_items_current['data'][0]);
|
rlm@3
|
186 $info_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
|
rlm@3
|
187 $info_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
|
rlm@3
|
188 break;
|
rlm@3
|
189
|
rlm@3
|
190 case 'tracknumber':
|
rlm@3
|
191 foreach ($info_ape_items_current['data'] as $comment) {
|
rlm@3
|
192 $info_ape['comments']['track'][] = $comment;
|
rlm@3
|
193 }
|
rlm@3
|
194 break;
|
rlm@3
|
195
|
rlm@3
|
196 default:
|
rlm@3
|
197 foreach ($info_ape_items_current['data'] as $comment) {
|
rlm@3
|
198 $info_ape['comments'][strtolower($item_key)][] = $comment;
|
rlm@3
|
199 }
|
rlm@3
|
200 break;
|
rlm@3
|
201 }
|
rlm@3
|
202
|
rlm@3
|
203 }
|
rlm@3
|
204 if (empty($info_replaygain)) {
|
rlm@3
|
205 unset($getid3->info['replay_gain']);
|
rlm@3
|
206 }
|
rlm@3
|
207
|
rlm@3
|
208 return true;
|
rlm@3
|
209 }
|
rlm@3
|
210
|
rlm@3
|
211
|
rlm@3
|
212
|
rlm@3
|
213 protected function ParseAPEheaderFooter($data, &$target) {
|
rlm@3
|
214
|
rlm@3
|
215 // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
|
rlm@3
|
216
|
rlm@3
|
217 if (substr($data, 0, 8) != 'APETAGEX') {
|
rlm@3
|
218 return false;
|
rlm@3
|
219 }
|
rlm@3
|
220
|
rlm@3
|
221 // shortcut
|
rlm@3
|
222 $target['raw'] = array ();
|
rlm@3
|
223 $target_raw = &$target['raw'];
|
rlm@3
|
224
|
rlm@3
|
225 $target_raw['footer_tag'] = 'APETAGEX';
|
rlm@3
|
226
|
rlm@3
|
227 getid3_lib::ReadSequence("LittleEndian2Int", $target_raw, $data, 8,
|
rlm@3
|
228 array (
|
rlm@3
|
229 'version' => 4,
|
rlm@3
|
230 'tagsize' => 4,
|
rlm@3
|
231 'tag_items' => 4,
|
rlm@3
|
232 'global_flags' => 4
|
rlm@3
|
233 )
|
rlm@3
|
234 );
|
rlm@3
|
235 $target_raw['reserved'] = substr($data, 24, 8);
|
rlm@3
|
236
|
rlm@3
|
237 $target['tag_version'] = $target_raw['version'] / 1000;
|
rlm@3
|
238 if ($target['tag_version'] >= 2) {
|
rlm@3
|
239
|
rlm@3
|
240 $target['flags'] = $this->ParseAPEtagFlags($target_raw['global_flags']);
|
rlm@3
|
241 }
|
rlm@3
|
242
|
rlm@3
|
243 return true;
|
rlm@3
|
244 }
|
rlm@3
|
245
|
rlm@3
|
246
|
rlm@3
|
247
|
rlm@3
|
248 protected function ParseAPEtagFlags($raw_flag_int) {
|
rlm@3
|
249
|
rlm@3
|
250 // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
|
rlm@3
|
251 // All are set to zero on creation and ignored on reading."
|
rlm@3
|
252 // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
|
rlm@3
|
253
|
rlm@3
|
254 $target['header'] = (bool) ($raw_flag_int & 0x80000000);
|
rlm@3
|
255 $target['footer'] = (bool) ($raw_flag_int & 0x40000000);
|
rlm@3
|
256 $target['this_is_header'] = (bool) ($raw_flag_int & 0x20000000);
|
rlm@3
|
257 $target['item_contents_raw'] = ($raw_flag_int & 0x00000006) >> 1;
|
rlm@3
|
258 $target['read_only'] = (bool) ($raw_flag_int & 0x00000001);
|
rlm@3
|
259
|
rlm@3
|
260 $target['item_contents'] = getid3_apetag::APEcontentTypeFlagLookup($target['item_contents_raw']);
|
rlm@3
|
261
|
rlm@3
|
262 return $target;
|
rlm@3
|
263 }
|
rlm@3
|
264
|
rlm@3
|
265
|
rlm@3
|
266
|
rlm@3
|
267 public static function APEcontentTypeFlagLookup($content_type_id) {
|
rlm@3
|
268
|
rlm@3
|
269 static $lookup = array (
|
rlm@3
|
270 0 => 'utf-8',
|
rlm@3
|
271 1 => 'binary',
|
rlm@3
|
272 2 => 'external',
|
rlm@3
|
273 3 => 'reserved'
|
rlm@3
|
274 );
|
rlm@3
|
275 return (isset($lookup[$content_type_id]) ? $lookup[$content_type_id] : 'invalid');
|
rlm@3
|
276 }
|
rlm@3
|
277
|
rlm@3
|
278
|
rlm@3
|
279
|
rlm@3
|
280 public static function APEtagItemIsUTF8Lookup($item_key) {
|
rlm@3
|
281
|
rlm@3
|
282 static $lookup = array (
|
rlm@3
|
283 'title',
|
rlm@3
|
284 'subtitle',
|
rlm@3
|
285 'artist',
|
rlm@3
|
286 'album',
|
rlm@3
|
287 'debut album',
|
rlm@3
|
288 'publisher',
|
rlm@3
|
289 'conductor',
|
rlm@3
|
290 'track',
|
rlm@3
|
291 'composer',
|
rlm@3
|
292 'comment',
|
rlm@3
|
293 'copyright',
|
rlm@3
|
294 'publicationright',
|
rlm@3
|
295 'file',
|
rlm@3
|
296 'year',
|
rlm@3
|
297 'record date',
|
rlm@3
|
298 'record location',
|
rlm@3
|
299 'genre',
|
rlm@3
|
300 'media',
|
rlm@3
|
301 'related',
|
rlm@3
|
302 'isrc',
|
rlm@3
|
303 'abstract',
|
rlm@3
|
304 'language',
|
rlm@3
|
305 'bibliography'
|
rlm@3
|
306 );
|
rlm@3
|
307 return in_array(strtolower($item_key), $lookup);
|
rlm@3
|
308 }
|
rlm@3
|
309
|
rlm@3
|
310 }
|
rlm@3
|
311
|
rlm@3
|
312 ?> |