~canonical-sysadmins/wordpress/4.7.4

« back to all changes in this revision

Viewing changes to wp-includes/ID3/module.tag.apetag.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
16
class getid3_apetag extends getid3_handler
 
17
{
 
18
        public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
 
19
        public $overrideendoffset  = 0;
 
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 APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
 
26
                        return false;
 
27
                }
 
28
 
 
29
                $id3v1tagsize     = 128;
 
30
                $apetagheadersize = 32;
 
31
                $lyrics3tagsize   = 10;
 
32
 
 
33
                if ($this->overrideendoffset == 0) {
 
34
 
 
35
                        fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
 
36
                        $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
 
37
 
 
38
                        //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
 
39
                        if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
 
40
 
 
41
                                // APE tag found before ID3v1
 
42
                                $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
 
43
 
 
44
                        //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
 
45
                        } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
 
46
 
 
47
                                // APE tag found, no ID3v1
 
48
                                $info['ape']['tag_offset_end'] = $info['filesize'];
 
49
 
 
50
                        }
 
51
 
 
52
                } else {
 
53
 
 
54
                        fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
 
55
                        if (fread($this->getid3->fp, 8) == 'APETAGEX') {
 
56
                                $info['ape']['tag_offset_end'] = $this->overrideendoffset;
 
57
                        }
 
58
 
 
59
                }
 
60
                if (!isset($info['ape']['tag_offset_end'])) {
 
61
 
 
62
                        // APE tag not found
 
63
                        unset($info['ape']);
 
64
                        return false;
 
65
 
 
66
                }
 
67
 
 
68
                // shortcut
 
69
                $thisfile_ape = &$info['ape'];
 
70
 
 
71
                fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
 
72
                $APEfooterData = fread($this->getid3->fp, 32);
 
73
                if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
 
74
                        $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
 
75
                        return false;
 
76
                }
 
77
 
 
78
                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
 
79
                        fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
 
80
                        $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
 
81
                        $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
 
82
                } else {
 
83
                        $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
 
84
                        fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
 
85
                        $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
 
86
                }
 
87
                $info['avdataend'] = $thisfile_ape['tag_offset_start'];
 
88
 
 
89
                if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
 
90
                        $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
 
91
                        unset($info['id3v1']);
 
92
                        foreach ($info['warning'] as $key => $value) {
 
93
                                if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
 
94
                                        unset($info['warning'][$key]);
 
95
                                        sort($info['warning']);
 
96
                                        break;
 
97
                                }
 
98
                        }
 
99
                }
 
100
 
 
101
                $offset = 0;
 
102
                if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
 
103
                        if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
 
104
                                $offset += $apetagheadersize;
 
105
                        } else {
 
106
                                $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
 
107
                                return false;
 
108
                        }
 
109
                }
 
110
 
 
111
                // shortcut
 
112
                $info['replay_gain'] = array();
 
113
                $thisfile_replaygain = &$info['replay_gain'];
 
114
 
 
115
                for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
 
116
                        $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
 
117
                        $offset += 4;
 
118
                        $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
 
119
                        $offset += 4;
 
120
                        if (strstr(substr($APEtagData, $offset), "\x00") === false) {
 
121
                                $info['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);
 
122
                                return false;
 
123
                        }
 
124
                        $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
 
125
                        $item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
 
126
 
 
127
                        // shortcut
 
128
                        $thisfile_ape['items'][$item_key] = array();
 
129
                        $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
 
130
 
 
131
                        $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
 
132
 
 
133
                        $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
 
134
                        $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
 
135
                        $offset += $value_size;
 
136
 
 
137
                        $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
 
138
                        switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
 
139
                                case 0: // UTF-8
 
140
                                case 3: // Locator (URL, filename, etc), UTF-8 encoded
 
141
                                        $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
 
142
                                        break;
 
143
 
 
144
                                default: // binary data
 
145
                                        break;
 
146
                        }
 
147
 
 
148
                        switch (strtolower($item_key)) {
 
149
                                case 'replaygain_track_gain':
 
150
                                        $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 
151
                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
 
152
                                        break;
 
153
 
 
154
                                case 'replaygain_track_peak':
 
155
                                        $thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 
156
                                        $thisfile_replaygain['track']['originator'] = 'unspecified';
 
157
                                        if ($thisfile_replaygain['track']['peak'] <= 0) {
 
158
                                                $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
 
159
                                        }
 
160
                                        break;
 
161
 
 
162
                                case 'replaygain_album_gain':
 
163
                                        $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 
164
                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
 
165
                                        break;
 
166
 
 
167
                                case 'replaygain_album_peak':
 
168
                                        $thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 
169
                                        $thisfile_replaygain['album']['originator'] = 'unspecified';
 
170
                                        if ($thisfile_replaygain['album']['peak'] <= 0) {
 
171
                                                $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
 
172
                                        }
 
173
                                        break;
 
174
 
 
175
                                case 'mp3gain_undo':
 
176
                                        list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
 
177
                                        $thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
 
178
                                        $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
 
179
                                        $thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
 
180
                                        break;
 
181
 
 
182
                                case 'mp3gain_minmax':
 
183
                                        list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
 
184
                                        $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
 
185
                                        $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
 
186
                                        break;
 
187
 
 
188
                                case 'mp3gain_album_minmax':
 
189
                                        list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
 
190
                                        $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
 
191
                                        $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
 
192
                                        break;
 
193
 
 
194
                                case 'tracknumber':
 
195
                                        if (is_array($thisfile_ape_items_current['data'])) {
 
196
                                                foreach ($thisfile_ape_items_current['data'] as $comment) {
 
197
                                                        $thisfile_ape['comments']['track'][] = $comment;
 
198
                                                }
 
199
                                        }
 
200
                                        break;
 
201
 
 
202
                                case 'cover art (artist)':
 
203
                                case 'cover art (back)':
 
204
                                case 'cover art (band logo)':
 
205
                                case 'cover art (band)':
 
206
                                case 'cover art (colored fish)':
 
207
                                case 'cover art (composer)':
 
208
                                case 'cover art (conductor)':
 
209
                                case 'cover art (front)':
 
210
                                case 'cover art (icon)':
 
211
                                case 'cover art (illustration)':
 
212
                                case 'cover art (lead)':
 
213
                                case 'cover art (leaflet)':
 
214
                                case 'cover art (lyricist)':
 
215
                                case 'cover art (media)':
 
216
                                case 'cover art (movie scene)':
 
217
                                case 'cover art (other icon)':
 
218
                                case 'cover art (other)':
 
219
                                case 'cover art (performance)':
 
220
                                case 'cover art (publisher logo)':
 
221
                                case 'cover art (recording)':
 
222
                                case 'cover art (studio)':
 
223
                                        // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
 
224
                                        list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
 
225
                                        $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
 
226
                                        $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
 
227
 
 
228
                                        $thisfile_ape_items_current['image_mime'] = '';
 
229
                                        $imageinfo = array();
 
230
                                        $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
 
231
                                        $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
 
232
 
 
233
                                        do {
 
234
                                                if ($this->inline_attachments === false) {
 
235
                                                        // skip entirely
 
236
                                                        unset($thisfile_ape_items_current['data']);
 
237
                                                        break;
 
238
                                                }
 
239
                                                if ($this->inline_attachments === true) {
 
240
                                                        // great
 
241
                                                } elseif (is_int($this->inline_attachments)) {
 
242
                                                        if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
 
243
                                                                // too big, skip
 
244
                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
 
245
                                                                unset($thisfile_ape_items_current['data']);
 
246
                                                                break;
 
247
                                                        }
 
248
                                                } elseif (is_string($this->inline_attachments)) {
 
249
                                                        $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
 
250
                                                        if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
 
251
                                                                // cannot write, skip
 
252
                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
 
253
                                                                unset($thisfile_ape_items_current['data']);
 
254
                                                                break;
 
255
                                                        }
 
256
                                                }
 
257
                                                // if we get this far, must be OK
 
258
                                                if (is_string($this->inline_attachments)) {
 
259
                                                        $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
 
260
                                                        if (!file_exists($destination_filename) || is_writable($destination_filename)) {
 
261
                                                                file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
 
262
                                                        } else {
 
263
                                                                $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
 
264
                                                        }
 
265
                                                        $thisfile_ape_items_current['data_filename'] = $destination_filename;
 
266
                                                        unset($thisfile_ape_items_current['data']);
 
267
                                                } else {
 
268
                                                        if (!isset($info['ape']['comments']['picture'])) {
 
269
                                                                $info['ape']['comments']['picture'] = array();
 
270
                                                        }
 
271
                                                        $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
 
272
                                                }
 
273
                                        } while (false);
 
274
                                        break;
 
275
 
 
276
                                default:
 
277
                                        if (is_array($thisfile_ape_items_current['data'])) {
 
278
                                                foreach ($thisfile_ape_items_current['data'] as $comment) {
 
279
                                                        $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
 
280
                                                }
 
281
                                        }
 
282
                                        break;
 
283
                        }
 
284
 
 
285
                }
 
286
                if (empty($thisfile_replaygain)) {
 
287
                        unset($info['replay_gain']);
 
288
                }
 
289
                return true;
 
290
        }
 
291
 
 
292
        public function parseAPEheaderFooter($APEheaderFooterData) {
 
293
                // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
 
294
 
 
295
                // shortcut
 
296
                $headerfooterinfo['raw'] = array();
 
297
                $headerfooterinfo_raw = &$headerfooterinfo['raw'];
 
298
 
 
299
                $headerfooterinfo_raw['footer_tag']   =                  substr($APEheaderFooterData,  0, 8);
 
300
                if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
 
301
                        return false;
 
302
                }
 
303
                $headerfooterinfo_raw['version']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData,  8, 4));
 
304
                $headerfooterinfo_raw['tagsize']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
 
305
                $headerfooterinfo_raw['tag_items']    = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
 
306
                $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
 
307
                $headerfooterinfo_raw['reserved']     =                              substr($APEheaderFooterData, 24, 8);
 
308
 
 
309
                $headerfooterinfo['tag_version']         = $headerfooterinfo_raw['version'] / 1000;
 
310
                if ($headerfooterinfo['tag_version'] >= 2) {
 
311
                        $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
 
312
                }
 
313
                return $headerfooterinfo;
 
314
        }
 
315
 
 
316
        public function parseAPEtagFlags($rawflagint) {
 
317
                // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
 
318
                // All are set to zero on creation and ignored on reading."
 
319
                // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
 
320
                $flags['header']            = (bool) ($rawflagint & 0x80000000);
 
321
                $flags['footer']            = (bool) ($rawflagint & 0x40000000);
 
322
                $flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
 
323
                $flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
 
324
                $flags['read_only']         = (bool) ($rawflagint & 0x00000001);
 
325
 
 
326
                $flags['item_contents']     = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
 
327
 
 
328
                return $flags;
 
329
        }
 
330
 
 
331
        public function APEcontentTypeFlagLookup($contenttypeid) {
 
332
                static $APEcontentTypeFlagLookup = array(
 
333
                        0 => 'utf-8',
 
334
                        1 => 'binary',
 
335
                        2 => 'external',
 
336
                        3 => 'reserved'
 
337
                );
 
338
                return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
 
339
        }
 
340
 
 
341
        public function APEtagItemIsUTF8Lookup($itemkey) {
 
342
                static $APEtagItemIsUTF8Lookup = array(
 
343
                        'title',
 
344
                        'subtitle',
 
345
                        'artist',
 
346
                        'album',
 
347
                        'debut album',
 
348
                        'publisher',
 
349
                        'conductor',
 
350
                        'track',
 
351
                        'composer',
 
352
                        'comment',
 
353
                        'copyright',
 
354
                        'publicationright',
 
355
                        'file',
 
356
                        'year',
 
357
                        'record date',
 
358
                        'record location',
 
359
                        'genre',
 
360
                        'media',
 
361
                        'related',
 
362
                        'isrc',
 
363
                        'abstract',
 
364
                        'language',
 
365
                        'bibliography'
 
366
                );
 
367
                return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
 
368
        }
 
369
 
 
370
}