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

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

Change: getid3 upgraded to -> 1.7.9

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
File size: 11.5 KB
Line 
1<?php
2/////////////////////////////////////////////////////////////////
3/// getID3() by James Heinrich <info@getid3.org>               //
4//  available at http://getid3.sourceforge.net                 //
5//            or http://www.getid3.org                         //
6/////////////////////////////////////////////////////////////////
7// See readme.txt for more details                             //
8/////////////////////////////////////////////////////////////////
9//                                                             //
10// module.tag.apetag.php                                       //
11// module for analyzing APE tags                               //
12// dependencies: NONE                                          //
13//                                                            ///
14/////////////////////////////////////////////////////////////////
15
16class getid3_apetag
17{
18
19        function getid3_apetag(&$fd, &$ThisFileInfo, $overrideendoffset=0) {
20
21                if ($ThisFileInfo['filesize'] >= pow(2, 31)) {
22                        $ThisFileInfo['warning'][] = 'Unable to check for APEtags because file is larger than 2GB';
23                        return false;
24                }
25
26                $id3v1tagsize     = 128;
27                $apetagheadersize = 32;
28                $lyrics3tagsize   = 10;
29
30                if ($overrideendoffset == 0) {
31
32                        fseek($fd, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
33                        $APEfooterID3v1 = fread($fd, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
34
35                        //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
36                        if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
37
38                                // APE tag found before ID3v1
39                                $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize'] - $id3v1tagsize;
40
41                        //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
42                        } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
43
44                                // APE tag found, no ID3v1
45                                $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize'];
46
47                        }
48
49                } else {
50
51                        fseek($fd, $overrideendoffset - $apetagheadersize, SEEK_SET);
52                        if (fread($fd, 8) == 'APETAGEX') {
53                                $ThisFileInfo['ape']['tag_offset_end'] = $overrideendoffset;
54                        }
55
56                }
57                if (!isset($ThisFileInfo['ape']['tag_offset_end'])) {
58
59                        // APE tag not found
60                        unset($ThisFileInfo['ape']);
61                        return false;
62
63                }
64
65                // shortcut
66                $thisfile_ape = &$ThisFileInfo['ape'];
67
68                fseek($fd, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
69                $APEfooterData = fread($fd, 32);
70                if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
71                        $ThisFileInfo['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
72                        return false;
73                }
74
75                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
76                        fseek($fd, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
77                        $thisfile_ape['tag_offset_start'] = ftell($fd);
78                        $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
79                } else {
80                        $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
81                        fseek($fd, $thisfile_ape['tag_offset_start'], SEEK_SET);
82                        $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize']);
83                }
84                $ThisFileInfo['avdataend'] = $thisfile_ape['tag_offset_start'];
85
86                if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
87                        $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
88                        unset($ThisFileInfo['id3v1']);
89                        foreach ($ThisFileInfo['warning'] as $key => $value) {
90                                if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
91                                        unset($ThisFileInfo['warning'][$key]);
92                                        sort($ThisFileInfo['warning']);
93                                        break;
94                                }
95                        }
96                }
97
98                $offset = 0;
99                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
100                        if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
101                                $offset += $apetagheadersize;
102                        } else {
103                                $ThisFileInfo['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
104                                return false;
105                        }
106                }
107
108                // shortcut
109                $ThisFileInfo['replay_gain'] = array();
110                $thisfile_replaygain = &$ThisFileInfo['replay_gain'];
111
112                for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
113                        $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
114                        $offset += 4;
115                        $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
116                        $offset += 4;
117                        if (strstr(substr($APEtagData, $offset), "\x00") === false) {
118                                $ThisFileInfo['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
119                                return false;
120                        }
121                        $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
122                        $item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
123
124                        // shortcut
125                        $thisfile_ape['items'][$item_key] = array();
126                        $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
127
128                        $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
129                        $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
130                        $offset += $value_size;
131
132                        $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
133                        switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
134                                case 0: // UTF-8
135                                case 3: // Locator (URL, filename, etc), UTF-8 encoded
136                                        $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
137                                        break;
138
139                                default: // binary data
140                                        break;
141                        }
142
143                        switch (strtolower($item_key)) {
144                                case 'replaygain_track_gain':
145                                        $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
146                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
147                                        break;
148
149                                case 'replaygain_track_peak':
150                                        $thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
151                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
152                                        if ($thisfile_replaygain['track']['peak'] <= 0) {
153                                                $ThisFileInfo['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
154                                        }
155                                        break;
156
157                                case 'replaygain_album_gain':
158                                        $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
159                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
160                                        break;
161
162                                case 'replaygain_album_peak':
163                                        $thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
164                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
165                                        if ($thisfile_replaygain['album']['peak'] <= 0) {
166                                                $ThisFileInfo['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
167                                        }
168                                        break;
169
170                                case 'mp3gain_undo':
171                                        list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
172                                        $thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
173                                        $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
174                                        $thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
175                                        break;
176
177                                case 'mp3gain_minmax':
178                                        list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
179                                        $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
180                                        $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
181                                        break;
182
183                                case 'mp3gain_album_minmax':
184                                        list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
185                                        $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
186                                        $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
187                                        break;
188
189                                case 'tracknumber':
190                                        foreach ($thisfile_ape_items_current['data'] as $comment) {
191                                                $thisfile_ape['comments']['track'][] = $comment;
192                                        }
193                                        break;
194
195                                default:
196                                        foreach ($thisfile_ape_items_current['data'] as $comment) {
197                                                $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
198                                        }
199                                        break;
200                        }
201
202                }
203                if (empty($thisfile_replaygain)) {
204                        unset($ThisFileInfo['replay_gain']);
205                }
206
207                return true;
208        }
209
210        function parseAPEheaderFooter($APEheaderFooterData) {
211                // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
212
213                // shortcut
214                $headerfooterinfo['raw'] = array();
215                $headerfooterinfo_raw = &$headerfooterinfo['raw'];
216
217                $headerfooterinfo_raw['footer_tag']   =                  substr($APEheaderFooterData,  0, 8);
218                if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
219                        return false;
220                }
221                $headerfooterinfo_raw['version']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData,  8, 4));
222                $headerfooterinfo_raw['tagsize']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
223                $headerfooterinfo_raw['tag_items']    = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
224                $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
225                $headerfooterinfo_raw['reserved']     =                              substr($APEheaderFooterData, 24, 8);
226
227                $headerfooterinfo['tag_version']         = $headerfooterinfo_raw['version'] / 1000;
228                if ($headerfooterinfo['tag_version'] >= 2) {
229                        $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
230                }
231                return $headerfooterinfo;
232        }
233
234        function parseAPEtagFlags($rawflagint) {
235                // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
236                // All are set to zero on creation and ignored on reading."
237                // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
238                $flags['header']            = (bool) ($rawflagint & 0x80000000);
239                $flags['footer']            = (bool) ($rawflagint & 0x40000000);
240                $flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
241                $flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
242                $flags['read_only']         = (bool) ($rawflagint & 0x00000001);
243
244                $flags['item_contents']     = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
245
246                return $flags;
247        }
248
249        function APEcontentTypeFlagLookup($contenttypeid) {
250                static $APEcontentTypeFlagLookup = array(
251                        0 => 'utf-8',
252                        1 => 'binary',
253                        2 => 'external',
254                        3 => 'reserved'
255                );
256                return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
257        }
258
259        function APEtagItemIsUTF8Lookup($itemkey) {
260                static $APEtagItemIsUTF8Lookup = array(
261                        'title',
262                        'subtitle',
263                        'artist',
264                        'album',
265                        'debut album',
266                        'publisher',
267                        'conductor',
268                        'track',
269                        'composer',
270                        'comment',
271                        'copyright',
272                        'publicationright',
273                        'file',
274                        'year',
275                        'record date',
276                        'record location',
277                        'genre',
278                        'media',
279                        'related',
280                        'isrc',
281                        'abstract',
282                        'language',
283                        'bibliography'
284                );
285                return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
286        }
287
288}
289
290?>
Note: See TracBrowser for help on using the repository browser.