source: extensions/piwigo_videojs/include/getid3/module.tag.apetag.php @ 24676

Last change on this file since 24676 was 24676, checked in by ddtddt, 11 years ago

[extensions] - piwigo_videojs - add file for translate

File size: 15.7 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 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}
Note: See TracBrowser for help on using the repository browser.