ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
getid3_apetag Class Reference

getID3() by James Heinrich info@.nosp@m.geti.nosp@m.d3.or.nosp@m.g // More...

+ Inheritance diagram for getid3_apetag:
+ Collaboration diagram for getid3_apetag:

Public Member Functions

 Analyze ()
 
 parseAPEheaderFooter ($APEheaderFooterData)
 
 parseAPEtagFlags ($rawflagint)
 
 APEcontentTypeFlagLookup ($contenttypeid)
 
 APEtagItemIsUTF8Lookup ($itemkey)
 
- Public Member Functions inherited from getid3_handler
 __construct (getID3 $getid3, $call_module=null)
 
 Analyze ()
 
 AnalyzeString ($string)
 
 setStringMode ($string)
 
 saveAttachment ($name, $offset, $length, $image_mime=null)
 

Data Fields

 $inline_attachments = true
 
 $overrideendoffset = 0
 

Additional Inherited Members

- Protected Member Functions inherited from getid3_handler
 ftell ()
 
 fread ($bytes)
 
 fseek ($bytes, $whence=SEEK_SET)
 
 feof ()
 
 isDependencyFor ($module)
 
 error ($text)
 
 warning ($text)
 
 notice ($text)
 
- Protected Attributes inherited from getid3_handler
 $getid3
 
 $data_string_flag = false
 
 $data_string = ''
 
 $data_string_position = 0
 
 $data_string_length = 0
 

Detailed Description

getID3() by James Heinrich info@.nosp@m.geti.nosp@m.d3.or.nosp@m.g //

Definition at line 17 of file module.tag.apetag.php.

Member Function Documentation

◆ Analyze()

getid3_apetag::Analyze ( )

Definition at line 22 of file module.tag.apetag.php.

References $comment, $i, $info, $key, $overrideendoffset, array, getid3_handler\error(), getid3_handler\fread(), getid3_handler\fseek(), getid3_handler\ftell(), getid3_lib\GetDataImageSize(), getid3_lib\intValueSupported(), getid3_lib\LittleEndian2Int(), parseAPEheaderFooter(), parseAPEtagFlags(), and getid3_handler\warning().

22  {
23  $info = &$this->getid3->info;
24 
25  if (!getid3_lib::intValueSupported($info['filesize'])) {
26  $this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
27  return false;
28  }
29 
30  $id3v1tagsize = 128;
31  $apetagheadersize = 32;
32  $lyrics3tagsize = 10;
33 
34  if ($this->overrideendoffset == 0) {
35 
36  $this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
37  $APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
38 
39  //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
40  if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
41 
42  // APE tag found before ID3v1
43  $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
44 
45  //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
46  } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
47 
48  // APE tag found, no ID3v1
49  $info['ape']['tag_offset_end'] = $info['filesize'];
50 
51  }
52 
53  } else {
54 
55  $this->fseek($this->overrideendoffset - $apetagheadersize);
56  if ($this->fread(8) == 'APETAGEX') {
57  $info['ape']['tag_offset_end'] = $this->overrideendoffset;
58  }
59 
60  }
61  if (!isset($info['ape']['tag_offset_end'])) {
62 
63  // APE tag not found
64  unset($info['ape']);
65  return false;
66 
67  }
68 
69  // shortcut
70  $thisfile_ape = &$info['ape'];
71 
72  $this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
73  $APEfooterData = $this->fread(32);
74  if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
75  $this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
76  return false;
77  }
78 
79  if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
80  $this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
81  $thisfile_ape['tag_offset_start'] = $this->ftell();
82  $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
83  } else {
84  $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
85  $this->fseek($thisfile_ape['tag_offset_start']);
86  $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
87  }
88  $info['avdataend'] = $thisfile_ape['tag_offset_start'];
89 
90  if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
91  $this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
92  unset($info['id3v1']);
93  foreach ($info['warning'] as $key => $value) {
94  if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
95  unset($info['warning'][$key]);
96  sort($info['warning']);
97  break;
98  }
99  }
100  }
101 
102  $offset = 0;
103  if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
104  if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
105  $offset += $apetagheadersize;
106  } else {
107  $this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
108  return false;
109  }
110  }
111 
112  // shortcut
113  $info['replay_gain'] = array();
114  $thisfile_replaygain = &$info['replay_gain'];
115 
116  for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
117  $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
118  $offset += 4;
119  $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
120  $offset += 4;
121  if (strstr(substr($APEtagData, $offset), "\x00") === false) {
122  $this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
123  return false;
124  }
125  $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
126  $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
127 
128  // shortcut
129  $thisfile_ape['items'][$item_key] = array();
130  $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
131 
132  $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
133 
134  $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
135  $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
136  $offset += $value_size;
137 
138  $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
139  switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
140  case 0: // UTF-8
141  case 2: // Locator (URL, filename, etc), UTF-8 encoded
142  $thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']);
143  break;
144 
145  case 1: // binary data
146  default:
147  break;
148  }
149 
150  switch (strtolower($item_key)) {
151  // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
152  case 'replaygain_track_gain':
153  if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
154  $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
155  $thisfile_replaygain['track']['originator'] = 'unspecified';
156  } else {
157  $this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
158  }
159  break;
160 
161  case 'replaygain_track_peak':
162  if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
163  $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
164  $thisfile_replaygain['track']['originator'] = 'unspecified';
165  if ($thisfile_replaygain['track']['peak'] <= 0) {
166  $this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
167  }
168  } else {
169  $this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
170  }
171  break;
172 
173  case 'replaygain_album_gain':
174  if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
175  $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
176  $thisfile_replaygain['album']['originator'] = 'unspecified';
177  } else {
178  $this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
179  }
180  break;
181 
182  case 'replaygain_album_peak':
183  if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
184  $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
185  $thisfile_replaygain['album']['originator'] = 'unspecified';
186  if ($thisfile_replaygain['album']['peak'] <= 0) {
187  $this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
188  }
189  } else {
190  $this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
191  }
192  break;
193 
194  case 'mp3gain_undo':
195  if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
196  list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
197  $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
198  $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
199  $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
200  } else {
201  $this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
202  }
203  break;
204 
205  case 'mp3gain_minmax':
206  if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
207  list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
208  $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
209  $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
210  } else {
211  $this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
212  }
213  break;
214 
215  case 'mp3gain_album_minmax':
216  if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
217  list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
218  $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
219  $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
220  } else {
221  $this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
222  }
223  break;
224 
225  case 'tracknumber':
226  if (is_array($thisfile_ape_items_current['data'])) {
227  foreach ($thisfile_ape_items_current['data'] as $comment) {
228  $thisfile_ape['comments']['track'][] = $comment;
229  }
230  }
231  break;
232 
233  case 'cover art (artist)':
234  case 'cover art (back)':
235  case 'cover art (band logo)':
236  case 'cover art (band)':
237  case 'cover art (colored fish)':
238  case 'cover art (composer)':
239  case 'cover art (conductor)':
240  case 'cover art (front)':
241  case 'cover art (icon)':
242  case 'cover art (illustration)':
243  case 'cover art (lead)':
244  case 'cover art (leaflet)':
245  case 'cover art (lyricist)':
246  case 'cover art (media)':
247  case 'cover art (movie scene)':
248  case 'cover art (other icon)':
249  case 'cover art (other)':
250  case 'cover art (performance)':
251  case 'cover art (publisher logo)':
252  case 'cover art (recording)':
253  case 'cover art (studio)':
254  // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
255  if (is_array($thisfile_ape_items_current['data'])) {
256  $this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
257  $thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
258  }
259  list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
260  $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
261  $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
262 
263  do {
264  $thisfile_ape_items_current['image_mime'] = '';
265  $imageinfo = array();
266  $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
267  if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
268  $this->warning('APEtag "'.$item_key.'" contains invalid image data');
269  break;
270  }
271  $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
272 
273  if ($this->inline_attachments === false) {
274  // skip entirely
275  unset($thisfile_ape_items_current['data']);
276  break;
277  }
278  if ($this->inline_attachments === true) {
279  // great
280  } elseif (is_int($this->inline_attachments)) {
281  if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
282  // too big, skip
283  $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
284  unset($thisfile_ape_items_current['data']);
285  break;
286  }
287  } elseif (is_string($this->inline_attachments)) {
288  $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
289  if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
290  // cannot write, skip
291  $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
292  unset($thisfile_ape_items_current['data']);
293  break;
294  }
295  }
296  // if we get this far, must be OK
297  if (is_string($this->inline_attachments)) {
298  $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
299  if (!file_exists($destination_filename) || is_writable($destination_filename)) {
300  file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
301  } else {
302  $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
303  }
304  $thisfile_ape_items_current['data_filename'] = $destination_filename;
305  unset($thisfile_ape_items_current['data']);
306  } else {
307  if (!isset($info['ape']['comments']['picture'])) {
308  $info['ape']['comments']['picture'] = array();
309  }
310  $comments_picture_data = array();
311  foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
312  if (isset($thisfile_ape_items_current[$picture_key])) {
313  $comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
314  }
315  }
316  $info['ape']['comments']['picture'][] = $comments_picture_data;
317  unset($comments_picture_data);
318  }
319  } while (false);
320  break;
321 
322  default:
323  if (is_array($thisfile_ape_items_current['data'])) {
324  foreach ($thisfile_ape_items_current['data'] as $comment) {
325  $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
326  }
327  }
328  break;
329  }
330 
331  }
332  if (empty($thisfile_replaygain)) {
333  unset($info['replay_gain']);
334  }
335  return true;
336  }
error($text)
Definition: getid3.php:1752
static intValueSupported($num)
Definition: getid3.lib.php:80
warning($text)
Definition: getid3.php:1758
static LittleEndian2Int($byteword, $signed=false)
Definition: getid3.lib.php:292
fread($bytes)
Definition: getid3.php:1683
static GetDataImageSize($imgData, &$imageinfo=array())
$comment
Definition: buildRTE.php:83
Create styles array
The data for the language used.
parseAPEtagFlags($rawflagint)
parseAPEheaderFooter($APEheaderFooterData)
$i
Definition: disco.tpl.php:19
fseek($bytes, $whence=SEEK_SET)
Definition: getid3.php:1711
$info
Definition: index.php:5
$key
Definition: croninfo.php:18
+ Here is the call graph for this function:

◆ APEcontentTypeFlagLookup()

getid3_apetag::APEcontentTypeFlagLookup (   $contenttypeid)

Definition at line 377 of file module.tag.apetag.php.

References array.

Referenced by parseAPEtagFlags().

377  {
378  static $APEcontentTypeFlagLookup = array(
379  0 => 'utf-8',
380  1 => 'binary',
381  2 => 'external',
382  3 => 'reserved'
383  );
384  return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
385  }
Create styles array
The data for the language used.
+ Here is the caller graph for this function:

◆ APEtagItemIsUTF8Lookup()

getid3_apetag::APEtagItemIsUTF8Lookup (   $itemkey)

Definition at line 387 of file module.tag.apetag.php.

References array.

387  {
388  static $APEtagItemIsUTF8Lookup = array(
389  'title',
390  'subtitle',
391  'artist',
392  'album',
393  'debut album',
394  'publisher',
395  'conductor',
396  'track',
397  'composer',
398  'comment',
399  'copyright',
400  'publicationright',
401  'file',
402  'year',
403  'record date',
404  'record location',
405  'genre',
406  'media',
407  'related',
408  'isrc',
409  'abstract',
410  'language',
411  'bibliography'
412  );
413  return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
414  }
Create styles array
The data for the language used.

◆ parseAPEheaderFooter()

getid3_apetag::parseAPEheaderFooter (   $APEheaderFooterData)

Definition at line 338 of file module.tag.apetag.php.

References array, getid3_lib\LittleEndian2Int(), and parseAPEtagFlags().

Referenced by Analyze().

338  {
339  // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
340 
341  // shortcut
342  $headerfooterinfo['raw'] = array();
343  $headerfooterinfo_raw = &$headerfooterinfo['raw'];
344 
345  $headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
346  if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
347  return false;
348  }
349  $headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
350  $headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
351  $headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
352  $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
353  $headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
354 
355  $headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
356  if ($headerfooterinfo['tag_version'] >= 2) {
357  $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
358  }
359  return $headerfooterinfo;
360  }
static LittleEndian2Int($byteword, $signed=false)
Definition: getid3.lib.php:292
Create styles array
The data for the language used.
parseAPEtagFlags($rawflagint)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseAPEtagFlags()

getid3_apetag::parseAPEtagFlags (   $rawflagint)

Definition at line 362 of file module.tag.apetag.php.

References APEcontentTypeFlagLookup().

Referenced by Analyze(), and parseAPEheaderFooter().

362  {
363  // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
364  // All are set to zero on creation and ignored on reading."
365  // http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
366  $flags['header'] = (bool) ($rawflagint & 0x80000000);
367  $flags['footer'] = (bool) ($rawflagint & 0x40000000);
368  $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
369  $flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
370  $flags['read_only'] = (bool) ($rawflagint & 0x00000001);
371 
372  $flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
373 
374  return $flags;
375  }
APEcontentTypeFlagLookup($contenttypeid)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Field Documentation

◆ $inline_attachments

getid3_apetag::$inline_attachments = true

Definition at line 19 of file module.tag.apetag.php.

◆ $overrideendoffset

getid3_apetag::$overrideendoffset = 0

Definition at line 20 of file module.tag.apetag.php.

Referenced by Analyze().


The documentation for this class was generated from the following file: