Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
mp3_file.inc
1 <?php
17 require_once SQ_CORE_PACKAGE_PATH.'/files/file/file.inc';
18 require_once SQ_DATA_PATH.'/private/conf/tools.inc';
19 
37 class MP3_File extends File
38 {
39 
40 
41  public $allowed_extensions = Array('mp3');
42 
43 
50  function __construct($assetid=0)
51  {
52  parent::__construct($assetid);
53 
54  }// end constructor
55 
56 
71  public function getAvailableKeywords()
72  {
73  $keywords = parent::getAvailableKeywords();
74 
75  $keywords['asset_attribute_length'] = 'Length of File (in Minutes:Seconds)';
76  $keywords['asset_attribute_length_seconds'] = 'Length of File (in Seconds)';
77 
78  $keywords['asset_attribute_song_dynamic'] = 'Dynamic MP3 Keyword for Songs Name';
79  $keywords['asset_attribute_album_dynamic'] = 'Dynamic MP3 Keyword for Album';
80  $keywords['asset_attribute_artist_dynamic'] = 'Dynamic MP3 Keyword for Artist';
81  $keywords['asset_attribute_track_dynamic'] = 'Dynamic MP3 Keyword for Track';
82  $keywords['asset_attribute_bitrate_dynamic'] = 'Dynamic MP3 Keyword for Bit Rate';
83  $keywords['asset_attribute_length_dynamic'] = 'Dynamic MP3 Keyword for Length (in Minutes:Seconds)';
84  $keywords['asset_attribute_length_seconds_dynamic'] = 'Dynamic MP3 Keyword for Length (in Seconds)';
85  $keywords['asset_attribute_year_dynamic'] = 'Dynamic MP3 Keyword for Year';
86  $keywords['asset_attribute_genre_dynamic'] = 'Dynamic MP3 Keyword for Genre';
87  $keywords['asset_attribute_samplerate_dynamic'] = 'Dynamic MP3 Keyword for Sample Rate';
88  $keywords['asset_attribute_composer_dynamic'] = 'Dynamic MP3 Keyword for Composer';
89  $keywords['asset_attribute_copyright_dynamic'] = 'Dynamic MP3 Keyword for Copyright';
90  $keywords['asset_attribute_comments_dynamic'] = 'Dynamic MP3 Keyword for Comments';
91  $keywords['asset_attribute_lyrics_dynamic'] = 'Dynamic MP3 Keyword for Lyrics';
92 
93  return $keywords;
94 
95  }// end getAvailableKeywords()
96 
97 
111  function getKeywordReplacement($keyword)
112  {
113  $replacement = NULL;
114 
115  // Remove any modifiers from keyword
116  $full_keyword = $keyword;
117  $keyword = parse_keyword($keyword, $modifiers);
118  $contextid = extract_context_modifier($modifiers);
119 
120  if ($contextid !== NULL) {
121  // If we were able to extract a context ID to change to, and it's
122  // different to our current one, then change and then reload a copy
123  // of the asset in that context (as we would need to do anyway)
124 
125  if ((int)$contextid !== $GLOBALS['SQ_SYSTEM']->getContextId()) {
126  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
127  $contexted_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($this->id);
128 
129  // Get the keyword without any modifiers
130  $replacement = $contexted_asset->getKeywordReplacement($keyword);
131 
132  // Then apply the ones we had remaining, then return it
133  apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $contexted_asset->id));
134 
135  unset($contexted_asset);
136  $GLOBALS['SQ_SYSTEM']->restoreContext();
137  return $replacement;
138 
139  }//end if contextid is not the currently active context
140 
141  }//end if contextid is not NULL
142 
143  // Static/overriding keywords
144  switch ($keyword)
145  {
146  case 'asset_attribute_length' :
147  // Length should be the time in minutes:seconds
148  $length = $this->attr('length');
149  $minutes = floor($length / 60);
150  $seconds = $length % 60;
151  $replacement = $minutes.':'.str_pad($seconds, 2, '0', STR_PAD_LEFT);
152  break;
153 
154  case 'asset_attribute_length_seconds' :
155  // Should also be able to provide time in seconds if we need to
156  $replacement = $this->attr('length');
157  break;
158 
159  }//end switch
160 
161  // Dynamic keywords lie below here. These strip the ID3 tags direct from
162  // the files, ignoring what is currently stored in the asset.
163  if (substr($keyword, -8) === '_dynamic') {
164  // Extract tag information - if failed then pretend it returned nothing
165  $tags = $this->extractID3Metadata();
166  if ($tags === FALSE) $tags = Array();
167 
168  // Get the actual attribute being dynamically requested
169  $sub_keyword = substr($keyword, 16, -8);
170  switch ($sub_keyword) {
171  case 'song':
172  $replacement = array_get_index($tags, 'title', '');
173  break;
174 
175  case 'album':
176  case 'artist':
177  case 'track':
178  case 'genre':
179  case 'year':
180  case 'composer' :
181  case 'comments' :
182  case 'copyright' :
183  case 'lyrics' :
184  $replacement = array_get_index($tags, $sub_keyword, '');
185  break;
186 
187  case 'bitrate':
188  $bitrate = array_get_index($tags, $sub_keyword, '');
189  if (is_numeric($bitrate)) {
190  // Bitrate is returned in bit/s, but we are displaying in
191  // kbit/s (rounded down)
192  $bitrate = floor($bitrate / 1000);
193  }
194  $replacement = $bitrate;
195  break;
196 
197  case 'samplerate':
198  $replacement = array_get_index($tags, 'sample_rate', '');
199  break;
200 
201  case 'length_seconds':
202  $length = array_get_index($tags, 'length', '');
203  if (is_numeric($length)) {
204  // Round off the number of seconds (up or down)
205  $length = round($length);
206  }
207  $replacement = $length;
208  break;
209 
210  case 'length':
211  $length = array_get_index($tags, 'length', '');
212  if (is_numeric($length)) {
213  // Round off the number of seconds (up or down)
214  $length = round($length);
215 
216  // Now format it
217  $minutes = floor($length / 60);
218  $seconds = $length % 60;
219  $length = $minutes.':'.str_pad($seconds, 2, '0', STR_PAD_LEFT);
220  }
221  $replacement = $length;
222  break;
223 
224  }//end switch
225 
226  }//end if dynamic keyword
227 
228  if ($replacement !== NULL) {
229  if (count($modifiers) > 0) {
230  apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $this->id));
231  }
232  } else {
233  // use full keyword so the modifiers still get used
234  $replacement = parent::getKeywordReplacement($full_keyword);
235  }
236 
237  return $replacement;
238 
239  }// end getKeywordReplacement()
240 
241 
254  public function saveAttributes($dont_run_updated=FALSE)
255  {
256  if ($this->attr('extract_id3')) {
257  $tags = $this->extractID3Metadata();
258  if ($tags !== FALSE) {
259 
260  // Go through the info we do have, setting what we can, and leaving
261  // alone what is not there.
262  foreach ($tags as $tag_name => $tag_value) {
263  switch ($tag_name) {
264  case 'title' :
265  // Named 'title' in our extraction function. Attribute is 'song'
266  // for backwards-compatibility reasons.
267  $this->setAttrValue('song', $tag_value);
268  break;
269 
270  case 'album' :
271  case 'artist' :
272  case 'track' :
273  case 'year' :
274  case 'genre' :
275  case 'composer' :
276  case 'copyright' :
277  case 'comments' :
278  case 'lyrics' :
279  // These just carry over to the respective attributes
280  $this->setAttrValue($tag_name, $tag_value);
281  break;
282 
283  case 'sample_rate' :
284  // Attribute had no underscore.
285  $this->setAttrValue('samplerate', $tag_value);
286  break;
287 
288  case 'bitrate' :
289  // Bitrate is provided in bit/s. We are saving this as a rounded-down
290  // bitrate in kbit/s instead.
291  $this->setAttrValue($tag_name, floor($tag_value / 1000));
292  break;
293 
294  case 'length' :
295  // Length is stored in fractional seconds. This will be rounded (up
296  // or down) to the nearest whole second.
297  $this->setAttrValue($tag_name, round($tag_value));
298  break;
299 
300  }//end switch
301 
302  }//end foreach
303 
304  }//end if tags returned
305 
306  // Set our extraction ID3 option back to NO so that changed values don't get overwritten
307  $this->setAttrValue('extract_id3', FALSE);
308  }//end if extract ID3 is true
309 
310 
311  // If we should write data back to the file
312  if ($this->attr('write_mp3') && SQ_TOOL_GETID3_ENABLED) {
313 
314  // Get our ID3 object and path
315  $id3 = $this->initID3();
316  $file_path = $this->getFilePath();
317 
318  // Set encoding
319  $format = 'UTF-8';
320  $id3->setOption(array('encoding'=>$format));
321 
322  // Initialize getID3 tag-writing module
323  require_once SQ_TOOL_GETID3_PATH.'/write.php';
324  $tagwriter = new getid3_writetags;
325  $tagwriter->filename = $file_path;
326  $tagwriter->tagformats = array('id3v1', 'id3v2.3');
327 
328  // set various options (optional)
329  $tagwriter->tag_encoding = $format;
330 
331  // populate data array
332  $tag_data['title'][] = $this->attr('song');
333  $tag_data['album'][] = $this->attr('album');
334  $tag_data['artist'][] = $this->attr('artist');
335  $tag_data['track'][] = $this->attr('track');
336  $tag_data['year'][] = $this->attr('year');
337  $tag_data['genre'][] = $this->attr('genre');
338  $tag_data['composer'][] = $this->attr('composer');
339  $tag_data['copyright'][] = $this->attr('copyright');
340  $tag_data['comment'][] = $this->attr('comments');
341  $tag_data['unsynchronised_lyrics'][] = $this->attr('lyrics');
342 
343  $tagwriter->tag_data = $tag_data;
344 
345  // Write tags and check for errors
346  if ($tagwriter->WriteTags()) {
347  if (!empty($tagwriter->warnings)) {
348  trigger_error('There were some ID3 warnings:<br />'.implode('<br /><br />', $tagwriter->warnings), E_USER_WARNING);
349  return FALSE;
350  }
351  } else {
352  trigger_error('Failed to write ID3 tags:<br />'.implode('<br /><br />', $tagwriter->errors), E_USER_WARNING);
353  return FALSE;
354  }//end else
355 
356  // Set our write ID3 option back to NO so that changed values don't get overwritten
357  $this->setAttrValue('write_mp3', FALSE);
358  }//end if
359 
360  return parent::saveAttributes($dont_run_updated);
361 
362  }//end saveAttributes()
363 
364 
372  public function getFilePath($file_name=NULL)
373  {
374  if (is_null($file_name)) {
375  if ($this->usePublicPath()) {
376  $file_name = $this->data_path_public.'/'.$this->attr('name');
377  } else {
378  $file_name = $this->data_path.'/'.$this->attr('name');
379  }
380  }//end if
381 
382  return $file_name;
383 
384  }//end getFilePath()
385 
386 
393  public function initID3()
394  {
395 
396  $file_name = $this->getFilePath();
397 
398  // getID3 tool disabled in External Tools? Silently fail.
399  if (!SQ_TOOL_GETID3_ENABLED) return FALSE;
400 
401  // getID3 external tool directory does not exist?
402  if (!is_dir(SQ_TOOL_GETID3_PATH)) {
403  trigger_error('Cannot extract ID3 metadata; path to getID3() external tool does not exist or is not a directory', E_USER_WARNING);
404  return FALSE;
405  }
406 
407  // ...or is not a getID3 module at all?
408  if (!is_file(SQ_TOOL_GETID3_PATH.'/getid3.php')) {
409  trigger_error('Cannot extract ID3 metadata; path to getID3() external tool does not point to a valid getID3() install', E_USER_WARNING);
410  return FALSE;
411  }
412 
413  if (!is_file($file_name)) {
414  trigger_error('Cannot extract ID3 metadata; MP3 file path provided does not exist', E_USER_WARNING);
415  }
416 
417  require_once SQ_TOOL_GETID3_PATH.'/getid3.php';
418 
419  // Now get a new getID3() object and analyse the stored file
420  $id3 = new getID3;
421  $id3->Analyze($file_name);
422 
423  if ($id3->info['fileformat'] !== 'mp3') {
424  trigger_error('Cannot extract ID3 metadata; File path passed is not an MP3 file', E_USER_WARNING);
425  return FALSE;
426  }
427 
428  return $id3;
429 
430  }//end initID3()
431 
432 
461  public function extractID3Metadata($file_name=NULL)
462  {
463  // Get our ID3 object
464  $id3 = $this->initID3();
465 
466  // Data relating to the audio itself.
467  // Channels stored as number rather than "mono/stereo" flag - to accommodate
468  // the rare MP3 Surround file that uses 5/6 channels.
469  $audio_data = Array(
470  'length' => $id3->info['playtime_seconds'],
471  'bitrate' => $id3->info['bitrate'],
472  'sample_rate' => $id3->info['audio']['sample_rate'],
473  'is_vbr' => ($id3->info['audio']['bitrate_mode'] === 'vbr'),
474  'channels' => $id3->info['audio']['channels'],
475  );
476 
477  $id3_tag_data = Array();
478 
479  if (isset($id3->info['tags']['id3v2'])) {
480  $tag =& $id3->info['tags']['id3v2'];
481 
482  // Only use the first listed entry for each tag
483  foreach ($tag as $tag_index => $tag_value) {
484  if (is_array($tag_value)) {
485  $tag[$tag_index] = $tag_value[0];
486  }
487  }
488 
489  if (isset($tag['content_type'])) {
490  $genres = getID3_ID3v2::ParseID3v2GenreString($tag['content_type']);
491  if (count($genres) > 0) {
492  $tag['genre'] = $genres['genre'][0];
493  }
494  }
495 
496  if (isset($tag['track_number'])) {
497  $tag['track'] = $tag['track_number'];
498  }
499  } else if (isset($id3->info['tags']['id3v1'])) {
500  $tag =& $id3->info['tags']['id3v1'];
501 
502  // Only use the first listed entry for each tag
503  foreach ($tag as $tag_index => $tag_value) {
504  if (is_array($tag_value)) {
505  $tag[$tag_index] = $tag_value[0];
506  }
507  }
508  }
509 
510  if (isset($tag)) {
511  if (isset($tag['title'])) {
512  $id3_tag_data['title'] = $tag['title'];
513  }
514  if (isset($tag['artist'])) {
515  $id3_tag_data['artist'] = $tag['artist'];
516  }
517  if (isset($tag['album'])) {
518  $id3_tag_data['album'] = $tag['album'];
519  }
520  if (isset($tag['track'])) {
521  $id3_tag_data['track'] = $tag['track'];
522  }
523  if (isset($tag['genre'])) {
524  $id3_tag_data['genre'] = $tag['genre'];
525  }
526  if (isset($tag['year'])) {
527  $id3_tag_data['year'] = $tag['year'];
528  }
529  if (isset($tag['composer'])) {
530  $id3_tag_data['composer'] = $tag['composer'];
531  }
532  if (isset($tag['copyright_message'])) {
533  $id3_tag_data['copyright'] = $tag['copyright_message'];
534  }
535  if (isset($tag['comments'])) {
536  $id3_tag_data['comments'] = $tag['comments'];
537  }
538  if (isset($tag['unsynchronised_lyric'])) {
539  $id3_tag_data['unsynchronised_lyric'] = $tag['unsynchronised_lyric'];
540  }
541  }
542 
543  $data = array_merge($audio_data, $id3_tag_data);
544  return $data;
545 
546  }//end extractID3Metadata()
547 
548 
549 }// end class
550 ?>