source: extensions/charlies_content/getid3/getid3/module.tag.apetag.php @ 3318

Last change on this file since 3318 was 3318, checked in by vdigital, 15 years ago

+ Add Charlies' content to depository

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