PageRenderTime 29ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/class/vainfo.class.php

https://gitlab.com/x33n/ampache
PHP | 1249 lines | 981 code | 104 blank | 164 comment | 83 complexity | 345d6be8ed2010c012e613fb3b9b51d7 MD5 | raw file
  1. <?php
  2. /* vim:set softtabstop=4 shiftwidth=4 expandtab: */
  3. /**
  4. *
  5. * LICENSE: GNU General Public License, version 2 (GPLv2)
  6. * Copyright 2001 - 2015 Ampache.org
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License v2
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. *
  21. */
  22. /**
  23. *
  24. * This class takes the information pulled from getID3 and returns it in a
  25. * Ampache-friendly way.
  26. *
  27. */
  28. class vainfo
  29. {
  30. public $encoding = '';
  31. public $encoding_id3v1 = '';
  32. public $encoding_id3v2 = '';
  33. public $filename = '';
  34. public $type = '';
  35. public $tags = array();
  36. public $islocal;
  37. public $gather_types = array();
  38. protected $_raw = array();
  39. protected $_getID3 = null;
  40. protected $_forcedSize = 0;
  41. protected $_file_encoding = '';
  42. protected $_file_pattern = '';
  43. protected $_dir_pattern = '';
  44. private $_pathinfo;
  45. private $_broken = false;
  46. /**
  47. * Constructor
  48. *
  49. * This function just sets up the class, it doesn't pull the information.
  50. *
  51. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  52. */
  53. public function __construct($file, $gather_types = array(), $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true)
  54. {
  55. $this->islocal = $islocal;
  56. $this->filename = $file;
  57. $this->gather_types = $gather_types;
  58. $this->encoding = $encoding ?: AmpConfig::get('site_charset');
  59. /* These are needed for the filename mojo */
  60. $this->_file_pattern = $file_pattern;
  61. $this->_dir_pattern = $dir_pattern;
  62. // FIXME: This looks ugly and probably wrong
  63. if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
  64. $this->_pathinfo = str_replace('%3A', ':', urlencode($this->filename));
  65. $this->_pathinfo = pathinfo(str_replace('%5C', '\\', $this->_pathinfo));
  66. } else {
  67. $this->_pathinfo = pathinfo(str_replace('%2F', '/', urlencode($this->filename)));
  68. }
  69. $this->_pathinfo['extension'] = strtolower($this->_pathinfo['extension']);
  70. if ($this->islocal) {
  71. // Initialize getID3 engine
  72. $this->_getID3 = new getID3();
  73. $this->_getID3->option_md5_data = false;
  74. $this->_getID3->option_md5_data_source = false;
  75. $this->_getID3->option_tags_html = false;
  76. $this->_getID3->option_extra_info = true;
  77. $this->_getID3->option_tag_lyrics3 = true;
  78. $this->_getID3->option_tags_process = true;
  79. $this->_getID3->option_tag_apetag = true;
  80. $this->_getID3->encoding = $this->encoding;
  81. // get id3tag encoding (try to work around off-spec id3v1 tags)
  82. try {
  83. $this->_raw = $this->_getID3->analyze(Core::conv_lc_file($file));
  84. } catch (Exception $error) {
  85. debug_event('getID3', "Broken file detected: $file: " . $error->getMessage(), 1);
  86. $this->_broken = true;
  87. return false;
  88. }
  89. if (AmpConfig::get('mb_detect_order')) {
  90. $mb_order = AmpConfig::get('mb_detect_order');
  91. } elseif (function_exists('mb_detect_order')) {
  92. $mb_order = implode(", ", mb_detect_order());
  93. } else {
  94. $mb_order = "auto";
  95. }
  96. $test_tags = array('artist', 'album', 'genre', 'title');
  97. if ($encoding_id3v1) {
  98. $this->encoding_id3v1 = $encoding_id3v1;
  99. } else {
  100. $tags = array();
  101. foreach ($test_tags as $tag) {
  102. if ($value = $this->_raw['id3v1'][$tag]) {
  103. $tags[$tag] = $value;
  104. }
  105. }
  106. $this->encoding_id3v1 = self::_detect_encoding($tags, $mb_order);
  107. }
  108. if (AmpConfig::get('getid3_detect_id3v2_encoding')) {
  109. // The user has told us to be moronic, so let's do that thing
  110. $tags = array();
  111. foreach ($test_tags as $tag) {
  112. if ($value = $this->_raw['id3v2']['comments'][$tag]) {
  113. $tags[$tag] = $value;
  114. }
  115. }
  116. $this->encoding_id3v2 = self::_detect_encoding($tags, $mb_order);
  117. $this->_getID3->encoding = $this->encoding_id3v2;
  118. }
  119. $this->_getID3->encoding_id3v1 = $this->encoding_id3v1;
  120. }
  121. }
  122. public function forceSize($size)
  123. {
  124. $this->_forcedSize = $size;
  125. }
  126. /**
  127. * _detect_encoding
  128. *
  129. * Takes an array of tags and attempts to automatically detect their
  130. * encoding.
  131. */
  132. private static function _detect_encoding($tags, $mb_order)
  133. {
  134. if (!function_exists('mb_detect_encoding'))
  135. return 'ISO-8859-1';
  136. $encodings = array();
  137. if (is_array($tags)) {
  138. foreach ($tags as $tag) {
  139. if (is_array($tag))
  140. $tag = implode(" ", $tag);
  141. $enc = mb_detect_encoding($tag, $mb_order, true);
  142. if ($enc != false)
  143. $encodings[$enc]++;
  144. }
  145. } else {
  146. $enc = mb_detect_encoding($tags, $mb_order, true);
  147. if ($enc != false)
  148. $encodings[$enc]++;
  149. }
  150. //!!debug_event('vainfo', 'encoding detection: ' . json_encode($encodings), 5);
  151. $high = 0;
  152. $encoding = 'ISO-8859-1';
  153. foreach ($encodings as $key => $value) {
  154. if ($value > $high) {
  155. $encoding = $key;
  156. $high = $value;
  157. }
  158. }
  159. if ($encoding != 'ASCII') {
  160. return (string) $encoding;
  161. } else {
  162. return 'ISO-8859-1';
  163. }
  164. }
  165. /**
  166. * get_info
  167. *
  168. * This function runs the various steps to gathering the metadata
  169. */
  170. public function get_info()
  171. {
  172. // If this is broken, don't waste time figuring it out a second
  173. // time, just return their rotting carcass of a media file.
  174. if ($this->_broken) {
  175. $this->tags = $this->set_broken();
  176. return true;
  177. }
  178. if ($this->islocal) {
  179. try {
  180. $this->_raw = $this->_getID3->analyze(Core::conv_lc_file($this->filename));
  181. } catch (Exception $error) {
  182. debug_event('getID2', 'Unable to catalog file: ' . $error->getMessage(), 1);
  183. }
  184. }
  185. /* Figure out what type of file we are dealing with */
  186. $this->type = $this->_get_type();
  187. $enabled_sources = (array) $this->get_metadata_order();
  188. if (in_array('filename', $enabled_sources)) {
  189. $this->tags['filename'] = $this->_parse_filename($this->filename);
  190. }
  191. if (in_array('getID3', $enabled_sources) && $this->islocal) {
  192. $this->tags['getID3'] = $this->_get_tags();
  193. }
  194. $this->_get_plugin_tags();
  195. } // get_info
  196. /*
  197. * write_id3
  198. * This function runs the various steps to gathering the metadata
  199. */
  200. public function write_id3($data)
  201. {
  202. // Get the Raw file information
  203. $this->read_id3();
  204. if (isset($this->_raw['tags']['id3v2'])) {
  205. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'write.php', __FILE__, true);
  206. $tagWriter = new getid3_writetags();
  207. $tagWriter->filename = $this->filename;
  208. //'id3v2.4' doesn't saves the year;
  209. $tagWriter->tagformats = array('id3v1', 'id3v2.3');
  210. $tagWriter->overwrite_tags = true;
  211. $tagWriter->remove_other_tags = true;
  212. $tagWriter->tag_encoding = 'UTF-8';
  213. $TagData = $this->_raw['tags']['id3v2'];
  214. // Foreach what we've got
  215. foreach ($data as $key=>$value) {
  216. if ($key != 'APIC') {
  217. $TagData[$key][0] = $value;
  218. }
  219. }
  220. if (isset($data['APIC'])) {
  221. $TagData['attached_picture'][0]['data'] = $data['APIC']['data'];
  222. $TagData['attached_picture'][0]['picturetypeid'] = '3';
  223. $TagData['attached_picture'][0]['description'] = 'Cover';
  224. $TagData['attached_picture'][0]['mime'] = $data['APIC']['mime'];
  225. }
  226. $tagWriter->tag_data = $TagData;
  227. if ($tagWriter->WriteTags()) {
  228. if (!empty($tagWriter->warnings)) {
  229. debug_event('vainfo' , 'FWarnings ' . implode("\n", $tagWriter->warnings), 5);
  230. }
  231. } else {
  232. debug_event('vainfo' , 'Failed to write tags! ' . implode("\n", $tagWriter->errors), 5);
  233. }
  234. }
  235. } // write_id3
  236. /**
  237. * read_id3
  238. * This function runs the various steps to gathering the metadata
  239. */
  240. public function read_id3()
  241. {
  242. // Get the Raw file information
  243. try {
  244. $this->_raw = $this->_getID3->analyze($this->filename);
  245. return $this->_raw;
  246. } catch (Exception $e) {
  247. debug_event('vainfo', "Unable to read file:" . $e->getMessage(), '1');
  248. }
  249. } // read_id3
  250. /**
  251. * get_tag_type
  252. *
  253. * This takes the result set and the tag_order defined in your config
  254. * file and tries to figure out which tag type(s) it should use. If your
  255. * tag_order doesn't match anything then it throws up its hands and uses
  256. * everything in random order.
  257. */
  258. public static function get_tag_type($results, $config_key = 'metadata_order')
  259. {
  260. $order = (array) AmpConfig::get($config_key);
  261. // Iterate through the defined key order adding them to an ordered array.
  262. $returned_keys = array();
  263. foreach ($order as $key) {
  264. if ($results[$key]) {
  265. $returned_keys[] = $key;
  266. }
  267. }
  268. // If we didn't find anything then default to everything.
  269. if (!isset($returned_keys)) {
  270. $returned_keys = array_keys($results);
  271. $returned_keys = sort($returned_keys);
  272. }
  273. // Unless they explicitly set it, add bitrate/mode/mime/etc.
  274. if (is_array($returned_keys)) {
  275. if (!in_array('general', $returned_keys)) {
  276. $returned_keys[] = 'general';
  277. }
  278. }
  279. return $returned_keys;
  280. }
  281. /**
  282. * clean_tag_info
  283. *
  284. * This function takes the array from vainfo along with the
  285. * key we've decided on and the filename and returns it in a
  286. * sanitized format that ampache can actually use
  287. */
  288. public static function clean_tag_info($results, $keys, $filename = null)
  289. {
  290. $info = array();
  291. //debug_event('vainfo', 'Clean tag info: ' . print_r($results, true), '5');
  292. $info['file'] = $filename;
  293. // Iteration!
  294. foreach ($keys as $key) {
  295. $tags = $results[$key];
  296. $info['file'] = $info['file'] ?: $tags['file'];
  297. $info['bitrate'] = $info['bitrate'] ?: intval($tags['bitrate']);
  298. $info['rate'] = $info['rate'] ?: intval($tags['rate']);
  299. $info['mode'] = $info['mode'] ?: $tags['mode'];
  300. $info['size'] = $info['size'] ?: $tags['size'];
  301. $info['mime'] = $info['mime'] ?: $tags['mime'];
  302. $info['encoding'] = $info['encoding'] ?: $tags['encoding'];
  303. $info['rating'] = $info['rating'] ?: $tags['rating'];
  304. $info['time'] = $info['time'] ?: intval($tags['time']);
  305. $info['channels'] = $info['channels'] ?: $tags['channels'];
  306. // This because video title are almost always bad...
  307. $info['original_name'] = $info['original_name'] ?: stripslashes(trim($tags['original_name']));
  308. $info['title'] = $info['title'] ?: stripslashes(trim($tags['title']));
  309. $info['year'] = $info['year'] ?: intval($tags['year']);
  310. $info['disk'] = $info['disk'] ?: intval($tags['disk']);
  311. $info['totaldisks'] = $info['totaldisks'] ?: intval($tags['totaldisks']);
  312. $info['artist'] = $info['artist'] ?: trim($tags['artist']);
  313. $info['albumartist'] = $info['albumartist'] ?: trim($tags['albumartist']);
  314. $info['album'] = $info['album'] ?: trim($tags['album']);
  315. $info['band'] = $info['band'] ?: trim($tags['band']);
  316. $info['composer'] = $info['composer'] ?: trim($tags['composer']);
  317. $info['publisher'] = $info['publisher'] ?: trim($tags['publisher']);
  318. $info['genre'] = self::clean_array_tag('genre', $info, $tags);
  319. $info['mb_trackid'] = $info['mb_trackid'] ?: trim($tags['mb_trackid']);
  320. $info['mb_albumid'] = $info['mb_albumid'] ?: trim($tags['mb_albumid']);
  321. $info['mb_albumid_group'] = $info['mb_albumid_group'] ?: trim($tags['mb_albumid_group']);
  322. $info['mb_artistid'] = $info['mb_artistid'] ?: trim($tags['mb_artistid']);
  323. $info['mb_albumartistid'] = $info['mb_albumartistid'] ?: trim($tags['mb_albumartistid']);
  324. $info['release_type'] = $info['release_type'] ?: trim($tags['release_type']);
  325. $info['language'] = $info['language'] ?: trim($tags['language']);
  326. $info['lyrics'] = $info['lyrics']
  327. ?: strip_tags(nl2br($tags['lyrics']), "<br>");
  328. $info['replaygain_track_gain'] = $info['replaygain_track_gain'] ?: floatval($tags['replaygain_track_gain']);
  329. $info['replaygain_track_peak'] = $info['replaygain_track_peak'] ?: floatval($tags['replaygain_track_peak']);
  330. $info['replaygain_album_gain'] = $info['replaygain_album_gain'] ?: floatval($tags['replaygain_album_gain']);
  331. $info['replaygain_album_peak'] = $info['replaygain_album_peak'] ?: floatval($tags['replaygain_album_peak']);
  332. $info['track'] = $info['track'] ?: intval($tags['track']);
  333. $info['resolution_x'] = $info['resolution_x'] ?: intval($tags['resolution_x']);
  334. $info['resolution_y'] = $info['resolution_y'] ?: intval($tags['resolution_y']);
  335. $info['display_x'] = $info['display_x'] ?: intval($tags['display_x']);
  336. $info['display_y'] = $info['display_y'] ?: intval($tags['display_y']);
  337. $info['frame_rate'] = $info['frame_rate'] ?: floatval($tags['frame_rate']);
  338. $info['video_bitrate'] = $info['video_bitrate'] ?: intval($tags['video_bitrate']);
  339. $info['audio_codec'] = $info['audio_codec'] ?: trim($tags['audio_codec']);
  340. $info['video_codec'] = $info['video_codec'] ?: trim($tags['video_codec']);
  341. $info['description'] = $info['description'] ?: trim($tags['description']);
  342. $info['tvshow'] = $info['tvshow'] ?: trim($tags['tvshow']);
  343. $info['tvshow_year'] = $info['tvshow_year'] ?: trim($tags['tvshow_year']);
  344. $info['tvshow_season'] = $info['tvshow_season'] ?: trim($tags['tvshow_season']);
  345. $info['tvshow_episode'] = $info['tvshow_episode'] ?: trim($tags['tvshow_episode']);
  346. $info['release_date'] = $info['release_date'] ?: trim($tags['release_date']);
  347. $info['tvshow_art'] = $info['tvshow_art'] ?: trim($tags['tvshow_art']);
  348. $info['tvshow_season_art'] = $info['tvshow_season_art'] ?: trim($tags['tvshow_season_art']);
  349. $info['art'] = $info['art'] ?: trim($tags['art']);
  350. }
  351. // Some things set the disk number even though there aren't multiple
  352. if ($info['totaldisks'] == 1 && $info['disk'] == 1) {
  353. unset($info['disk']);
  354. unset($info['totaldisks']);
  355. }
  356. return $info;
  357. }
  358. private static function clean_array_tag($field, $info, $tags)
  359. {
  360. $arr = array();
  361. if ((!$info[$field] || count($info[$field]) == 0) && $tags[$field]) {
  362. if (!is_array($tags[$field])) {
  363. // not all tag formats will return an array, but we need one
  364. $arr[] = trim($tags[$field]);
  365. } else {
  366. foreach ($tags[$field] as $genre) {
  367. $arr[] = trim($genre);
  368. }
  369. }
  370. } else {
  371. $arr = $info[$field];
  372. }
  373. return $arr;
  374. }
  375. /**
  376. * _get_type
  377. *
  378. * This function takes the raw information and figures out what type of
  379. * file we are dealing with.
  380. */
  381. private function _get_type()
  382. {
  383. // There are a few places that the file type can come from, in the end
  384. // we trust the encoding type.
  385. if ($type = $this->_raw['video']['dataformat']) {
  386. return $this->_clean_type($type);
  387. }
  388. if ($type = $this->_raw['audio']['streams']['0']['dataformat']) {
  389. return $this->_clean_type($type);
  390. }
  391. if ($type = $this->_raw['audio']['dataformat']) {
  392. return $this->_clean_type($type);
  393. }
  394. if ($type = $this->_raw['fileformat']) {
  395. return $this->_clean_type($type);
  396. }
  397. return false;
  398. }
  399. /**
  400. * _get_tags
  401. *
  402. * This processes the raw getID3 output and bakes it.
  403. */
  404. private function _get_tags()
  405. {
  406. $results = array();
  407. // The tags can come in many different shapes and colors
  408. // depending on the encoding time of day and phase of the moon.
  409. if (is_array($this->_raw['tags'])) {
  410. foreach ($this->_raw['tags'] as $key => $tag_array) {
  411. switch ($key) {
  412. case 'ape':
  413. case 'avi':
  414. case 'flv':
  415. case 'matroska':
  416. debug_event('vainfo', 'Cleaning ' . $key, 5);
  417. $parsed = $this->_cleanup_generic($tag_array);
  418. break;
  419. case 'vorbiscomment':
  420. debug_event('vainfo', 'Cleaning vorbis', 5);
  421. $parsed = $this->_cleanup_vorbiscomment($tag_array);
  422. break;
  423. case 'id3v1':
  424. debug_event('vainfo', 'Cleaning id3v1', 5);
  425. $parsed = $this->_cleanup_id3v1($tag_array);
  426. break;
  427. case 'id3v2':
  428. debug_event('vainfo', 'Cleaning id3v2', 5);
  429. $parsed = $this->_cleanup_id3v2($tag_array);
  430. break;
  431. case 'quicktime':
  432. debug_event('vainfo', 'Cleaning quicktime', 5);
  433. $parsed = $this->_cleanup_quicktime($tag_array);
  434. break;
  435. case 'riff':
  436. debug_event('vainfo', 'Cleaning riff', 5);
  437. $parsed = $this->_cleanup_riff($tag_array);
  438. break;
  439. case 'mpg':
  440. case 'mpeg':
  441. $key = 'mpeg';
  442. debug_event('vainfo', 'Cleaning MPEG', 5);
  443. $parsed = $this->_cleanup_generic($tag_array);
  444. break;
  445. case 'asf':
  446. case 'wmv':
  447. $key = 'asf';
  448. debug_event('vainfo', 'Cleaning WMV/WMA/ASF', 5);
  449. $parsed = $this->_cleanup_generic($tag_array);
  450. break;
  451. case 'lyrics3':
  452. debug_event('vainfo', 'Cleaning lyrics3', 5);
  453. $parsed = $this->_cleanup_lyrics($tag_array);
  454. break;
  455. default:
  456. debug_event('vainfo', 'Cleaning unrecognised tag type ' . $key . ' for file ' . $this->filename, 5);
  457. $parsed = $this->_cleanup_generic($tag_array);
  458. break;
  459. }
  460. $results[$key] = $parsed;
  461. }
  462. }
  463. $results['general'] = $this->_parse_general($this->_raw);
  464. $cleaned = self::clean_tag_info($results, self::get_tag_type($results, 'getid3_tag_order'), $this->filename);
  465. $cleaned['raw'] = $results;
  466. return $cleaned;
  467. }
  468. private function get_metadata_order_key()
  469. {
  470. if (!in_array('music', $this->gather_types)) {
  471. return 'metadata_order_video';
  472. }
  473. return 'metadata_order';
  474. }
  475. private function get_metadata_order()
  476. {
  477. return (array) AmpConfig::get($this->get_metadata_order_key());
  478. }
  479. /**
  480. * _get_plugin_tags
  481. *
  482. * Get additional metadata from plugins
  483. */
  484. private function _get_plugin_tags()
  485. {
  486. $tag_order = $this->get_metadata_order();
  487. if (!is_array($tag_order)) {
  488. $tag_order = array($tag_order);
  489. }
  490. $plugin_names = Plugin::get_plugins('get_metadata');
  491. foreach ($tag_order as $tag_source) {
  492. if (in_array($tag_source, $plugin_names)) {
  493. $plugin = new Plugin($tag_source);
  494. $installed_version = Plugin::get_plugin_version($plugin->_plugin->name);
  495. if ($installed_version) {
  496. if ($plugin->load($GLOBALS['user'])) {
  497. $this->tags[$tag_source] = $plugin->_plugin->get_metadata($this->gather_types, self::clean_tag_info($this->tags, self::get_tag_type($this->tags, $this->get_metadata_order_key()), $this->filename));
  498. }
  499. }
  500. }
  501. }
  502. }
  503. /**
  504. * _parse_general
  505. *
  506. * Gather and return the general information about a file (vbr/cbr,
  507. * sample rate, channels, etc.)
  508. */
  509. private function _parse_general($tags)
  510. {
  511. $parsed = array();
  512. $parsed['title'] = urldecode($this->_pathinfo['filename']);
  513. $parsed['mode'] = $tags['audio']['bitrate_mode'];
  514. if ($parsed['mode'] == 'con') {
  515. $parsed['mode'] = 'cbr';
  516. }
  517. $parsed['bitrate'] = $tags['audio']['bitrate'];
  518. $parsed['channels'] = intval($tags['audio']['channels']);
  519. $parsed['rate'] = intval($tags['audio']['sample_rate']);
  520. $parsed['size'] = $this->_forcedSize ?: $tags['filesize'];
  521. $parsed['encoding'] = $tags['encoding'];
  522. $parsed['mime'] = $tags['mime_type'];
  523. $parsed['time'] = ($this->_forcedSize ? ((($this->_forcedSize - $tags['avdataoffset']) * 8) / $tags['bitrate']) : $tags['playtime_seconds']);
  524. $parsed['audio_codec'] = $tags['audio']['dataformat'];
  525. $parsed['video_codec'] = $tags['video']['dataformat'];
  526. $parsed['resolution_x'] = $tags['video']['resolution_x'];
  527. $parsed['resolution_y'] = $tags['video']['resolution_y'];
  528. $parsed['display_x'] = $tags['video']['display_x'];
  529. $parsed['display_y'] = $tags['video']['display_y'];
  530. $parsed['frame_rate'] = $tags['video']['frame_rate'];
  531. $parsed['video_bitrate'] = $tags['video']['bitrate'];
  532. if (isset($tags['ape'])) {
  533. if (isset($tags['ape']['items'])) {
  534. foreach ($tags['ape']['items'] as $key => $tag) {
  535. switch (strtolower($key)) {
  536. case 'replaygain_track_gain':
  537. case 'replaygain_track_peak':
  538. case 'replaygain_album_gain':
  539. case 'replaygain_album_peak':
  540. $parsed[$key] = floatval($tag['data'][0]);
  541. break;
  542. }
  543. }
  544. }
  545. }
  546. return $parsed;
  547. }
  548. private function trimAscii($string)
  549. {
  550. return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', trim($string));
  551. }
  552. /**
  553. * _clean_type
  554. * This standardizes the type that we are given into a recognized type.
  555. */
  556. private function _clean_type($type)
  557. {
  558. switch ($type) {
  559. case 'mp3':
  560. case 'mp2':
  561. case 'mpeg3':
  562. return 'mp3';
  563. case 'vorbis':
  564. return 'ogg';
  565. case 'flac':
  566. case 'flv':
  567. case 'mpg':
  568. case 'mpeg':
  569. case 'asf':
  570. case 'wmv':
  571. case 'avi':
  572. case 'quicktime':
  573. return $type;
  574. default:
  575. /* Log the fact that we couldn't figure it out */
  576. debug_event('vainfo','Unable to determine file type from ' . $type . ' on file ' . $this->filename,'5');
  577. return $type;
  578. }
  579. }
  580. /**
  581. * _cleanup_generic
  582. *
  583. * This does generic cleanup.
  584. */
  585. private function _cleanup_generic($tags)
  586. {
  587. $parsed = array();
  588. foreach ($tags as $tagname => $data) {
  589. switch (strtolower($tagname)) {
  590. case 'genre':
  591. // Pass the array through
  592. $parsed[$tagname] = $this->parseGenres($data);
  593. break;
  594. case 'musicbrainz_artistid':
  595. $parsed['mb_artistid'] = $data[0];
  596. break;
  597. case 'musicbrainz_albumid':
  598. $parsed['mb_albumid'] = $data[0];
  599. break;
  600. case 'musicbrainz_albumartistid':
  601. $parsed['mb_albumartistid'] = $data[0];
  602. break;
  603. case 'musicbrainz_releasegroupid':
  604. $parsed['mb_albumid_group'] = $data[0];
  605. break;
  606. case 'musicbrainz_trackid':
  607. $parsed['mb_trackid'] = $data[0];
  608. break;
  609. case 'musicbrainz_albumtype':
  610. $parsed['release_type'] = $data[0];
  611. break;
  612. default:
  613. $parsed[$tagname] = $data[0];
  614. break;
  615. }
  616. }
  617. return $parsed;
  618. }
  619. /**
  620. * _cleanup_lyrics
  621. *
  622. * This is supposed to handle lyrics3. FIXME: does it?
  623. */
  624. private function _cleanup_lyrics($tags)
  625. {
  626. $parsed = array();
  627. foreach ($tags as $tag => $data) {
  628. if ($tag == 'unsyncedlyrics' || $tag == 'unsynced lyrics' || $tag == 'unsynchronised lyric') {
  629. $tag = 'lyrics';
  630. }
  631. $parsed[$tag] = $data[0];
  632. }
  633. return $parsed;
  634. }
  635. /**
  636. * _cleanup_vorbiscomment
  637. *
  638. * Standardises tag names from vorbis.
  639. */
  640. private function _cleanup_vorbiscomment($tags)
  641. {
  642. $parsed = array();
  643. foreach ($tags as $tag => $data) {
  644. switch (strtolower($tag)) {
  645. case 'genre':
  646. // Pass the array through
  647. $parsed[$tag] = $this->parseGenres($data);
  648. break;
  649. case 'tracknumber':
  650. $parsed['track'] = $data[0];
  651. break;
  652. case 'discnumber':
  653. $elements = explode('/', $data[0]);
  654. $parsed['disk'] = $elements[0];
  655. $parsed['totaldisks'] = $elements[1];
  656. break;
  657. case 'date':
  658. $parsed['year'] = $data[0];
  659. break;
  660. case 'musicbrainz_artistid':
  661. $parsed['mb_artistid'] = $data[0];
  662. break;
  663. case 'musicbrainz_albumid':
  664. $parsed['mb_albumid'] = $data[0];
  665. break;
  666. case 'musicbrainz_albumartistid':
  667. $parsed['mb_albumartistid'] = $data[0];
  668. break;
  669. case 'musicbrainz_releasegroupid':
  670. $parsed['mb_albumid_group'] = $data[0];
  671. break;
  672. case 'musicbrainz_trackid':
  673. $parsed['mb_trackid'] = $data[0];
  674. break;
  675. case 'musicbrainz_albumtype':
  676. $parsed['release_type'] = $data[0];
  677. break;
  678. case 'unsyncedlyrics':
  679. case 'unsynced lyrics':
  680. case 'lyrics':
  681. $parsed['lyrics'] = $data[0];
  682. break;
  683. default:
  684. $parsed[$tag] = $data[0];
  685. break;
  686. }
  687. }
  688. return $parsed;
  689. }
  690. /**
  691. * _cleanup_id3v1
  692. *
  693. * Doesn't do much.
  694. */
  695. private function _cleanup_id3v1($tags)
  696. {
  697. $parsed = array();
  698. foreach ($tags as $tag => $data) {
  699. // This is our baseline for naming so everything's already right,
  700. // we just need to shuffle off the array.
  701. $parsed[$tag] = $data[0];
  702. }
  703. return $parsed;
  704. }
  705. /**
  706. * _cleanup_id3v2
  707. *
  708. * Whee, v2!
  709. */
  710. private function _cleanup_id3v2($tags)
  711. {
  712. $parsed = array();
  713. foreach ($tags as $tag => $data) {
  714. switch ($tag) {
  715. case 'genre':
  716. $parsed['genre'] = $this->parseGenres($data);
  717. break;
  718. case 'part_of_a_set':
  719. $elements = explode('/', $data[0]);
  720. $parsed['disk'] = $elements[0];
  721. $parsed['totaldisks'] = $elements[1];
  722. break;
  723. case 'track_number':
  724. $parsed['track'] = $data[0];
  725. break;
  726. case 'comments':
  727. $parsed['comment'] = $data[0];
  728. break;
  729. case 'unsynchronised_lyric':
  730. $parsed['lyrics'] = $data[0];
  731. break;
  732. default:
  733. $parsed[$tag] = $data[0];
  734. break;
  735. }
  736. }
  737. // getID3 doesn't do all the parsing we need, so grab the raw data
  738. $id3v2 = $this->_raw['id3v2'];
  739. if (!empty($id3v2['UFID'])) {
  740. // Find the MBID for the track
  741. foreach ($id3v2['UFID'] as $ufid) {
  742. if ($ufid['ownerid'] == 'http://musicbrainz.org') {
  743. $parsed['mb_trackid'] = $ufid['data'];
  744. }
  745. }
  746. if (!empty($id3v2['TXXX'])) {
  747. // Find the MBIDs for the album and artist
  748. // Use trimAscii to remove noise (see #225 and #438 issues). Is this a GetID3 bug?
  749. foreach ($id3v2['TXXX'] as $txxx) {
  750. switch (strtolower($this->trimAscii($txxx['description']))) {
  751. case 'musicbrainz album id':
  752. $parsed['mb_albumid'] = $this->trimAscii($txxx['data']);
  753. break;
  754. case 'musicbrainz release group id':
  755. $parsed['mb_albumid_group'] = $this->trimAscii($txxx['data']);
  756. break;
  757. case 'musicbrainz artist id':
  758. $parsed['mb_artistid'] = $this->trimAscii($txxx['data']);
  759. break;
  760. case 'musicbrainz album artist id':
  761. $parsed['mb_albumartistid'] = $this->trimAscii($txxx['data']);
  762. break;
  763. case 'musicbrainz album type':
  764. $parsed['release_type'] = $this->trimAscii($txxx['data']);
  765. break;
  766. case 'catalognumber':
  767. $parsed['catalog_number'] = $this->trimAscii($txxx['data']);
  768. break;
  769. case 'replaygain_track_gain':
  770. $parsed['replaygain_track_gain'] = floatval($txxx['data']);
  771. break;
  772. case 'replaygain_track_peak':
  773. $parsed['replaygain_track_peak'] = floatval($txxx['data']);
  774. break;
  775. case 'replaygain_album_gain':
  776. $parsed['replaygain_album_gain'] = floatval($txxx['data']);
  777. break;
  778. case 'replaygain_album_peak':
  779. $parsed['replaygain_album_peak'] = floatval($txxx['data']);
  780. break;
  781. }
  782. }
  783. }
  784. }
  785. // Find the rating
  786. if (is_array($id3v2['POPM'])) {
  787. foreach ($id3v2['POPM'] as $popm) {
  788. if (array_key_exists('email', $popm) &&
  789. $user = User::get_from_email($popm['email'])) {
  790. if ($user) {
  791. // Ratings are out of 255; scale it
  792. $parsed['rating'][$user->id] = $popm['rating'] / 255 * 5;
  793. }
  794. }
  795. }
  796. }
  797. return $parsed;
  798. }
  799. /**
  800. * _cleanup_riff
  801. */
  802. private function _cleanup_riff($tags)
  803. {
  804. $parsed = array();
  805. foreach ($tags as $tag => $data) {
  806. switch ($tag) {
  807. case 'product':
  808. $parsed['album'] = $data[0];
  809. break;
  810. default:
  811. $parsed[$tag] = $data[0];
  812. break;
  813. }
  814. }
  815. return $parsed;
  816. }
  817. /**
  818. * _cleanup_quicktime
  819. */
  820. private function _cleanup_quicktime($tags)
  821. {
  822. $parsed = array();
  823. foreach ($tags as $tag => $data) {
  824. switch ($tag) {
  825. case 'creation_date':
  826. $parsed['release_date'] = strtotime(str_replace(" ", "", $data[0]));
  827. if (strlen($data['0']) > 4) {
  828. $data[0] = date('Y', $parsed['release_date']);
  829. }
  830. $parsed['year'] = $data[0];
  831. break;
  832. case 'MusicBrainz Track Id':
  833. $parsed['mb_trackid'] = $data[0];
  834. break;
  835. case 'MusicBrainz Album Id':
  836. $parsed['mb_albumid'] = $data[0];
  837. break;
  838. case 'MusicBrainz Album Artist Id':
  839. $parsed['mb_albumartistid'] = $data[0];
  840. break;
  841. case 'MusicBrainz Release Group Id':
  842. $parsed['mb_albumid_group'] = $data[0];
  843. break;
  844. case 'MusicBrainz Artist Id':
  845. $parsed['mb_artistid'] = $data[0];
  846. break;
  847. case 'MusicBrainz Album Type':
  848. $parsed['release_type'] = $data[0];
  849. break;
  850. case 'track_number':
  851. $parsed['track'] = $data[0];
  852. break;
  853. case 'disc_number':
  854. $parsed['disk'] = $data[0];
  855. break;
  856. case 'album_artist':
  857. $parsed['albumartist'] = $data[0];
  858. break;
  859. case 'tv_episode':
  860. $parsed['tvshow_episode'] = $data[0];
  861. break;
  862. case 'tv_season':
  863. $parsed['tvshow_season'] = $data[0];
  864. break;
  865. case 'tv_show_name':
  866. $parsed['tvshow'] = $data[0];
  867. break;
  868. default:
  869. $parsed[$tag] = $data[0];
  870. break;
  871. }
  872. }
  873. return $parsed;
  874. }
  875. /**
  876. * _parse_filename
  877. *
  878. * This function uses the file and directory patterns to pull out extra tag
  879. * information.
  880. */
  881. private function _parse_filename($filename)
  882. {
  883. $origin = $filename;
  884. $results = array();
  885. if (in_array('music', $this->gather_types) || in_array('clip', $this->gather_types)) {
  886. // Correctly detect the slash we need to use here
  887. if (strpos($filename, '/') !== false) {
  888. $slash_type = '/';
  889. $slash_type_preg = $slash_type;
  890. } else {
  891. $slash_type = '\\';
  892. $slash_type_preg = $slash_type . $slash_type;
  893. }
  894. // Combine the patterns
  895. $pattern = preg_quote($this->_dir_pattern) . $slash_type_preg . preg_quote($this->_file_pattern);
  896. // Remove first left directories from filename to match pattern
  897. $cntslash = substr_count($pattern, preg_quote($slash_type)) + 1;
  898. $filepart = explode($slash_type, $filename);
  899. if (count($filepart) > $cntslash) {
  900. $filename = implode($slash_type, array_slice($filepart, count($filepart) - $cntslash));
  901. }
  902. // Pull out the pattern codes into an array
  903. preg_match_all('/\%\w/', $pattern, $elements);
  904. // Mangle the pattern by turning the codes into regex captures
  905. $pattern = preg_replace('/\%[Ty]/', '([0-9]+?)', $pattern);
  906. $pattern = preg_replace('/\%\w/', '(.+?)', $pattern);
  907. $pattern = str_replace('/', '\/', $pattern);
  908. $pattern = str_replace(' ', '\s', $pattern);
  909. $pattern = '/' . $pattern . '\..+$/';
  910. // Pull out our actual matches
  911. preg_match($pattern, $filename, $matches);
  912. if ($matches != null) {
  913. // The first element is the full match text
  914. $matched = array_shift($matches);
  915. debug_event('vainfo', $pattern . ' matched ' . $matched . ' on ' . $filename, 5);
  916. // Iterate over what we found
  917. foreach ($matches as $key => $value) {
  918. $new_key = translate_pattern_code($elements['0'][$key]);
  919. if ($new_key) {
  920. $results[$new_key] = $value;
  921. }
  922. }
  923. $results['title'] = $results['title'] ?: basename($filename);
  924. if ($this->islocal) {
  925. $results['size'] = Core::get_filesize(Core::conv_lc_file($origin));
  926. }
  927. }
  928. }
  929. if (in_array('tvshow', $this->gather_types)) {
  930. $pathinfo = pathinfo($filename);
  931. $filetitle = $pathinfo['filename'];
  932. $results = array_merge($results, $this->parseEpisodeName($filetitle));
  933. if (!$results['tvshow']) {
  934. // Try to identify the show information from parent folder
  935. $filetitle = basename($pathinfo['dirname']);
  936. $results = array_merge($results, $this->parseEpisodeName($filetitle));
  937. if (!$results['tvshow']) {
  938. if ($results['tvshow_season'] && $results['tvshow_episode']) {
  939. // We have season and episode, we assume parent folder is the tvshow name
  940. $pathinfo = pathinfo($pathinfo['dirname']);
  941. $filetitle = basename($pathinfo['dirname']);
  942. $results['tvshow'] = $this->fixSerieName($filetitle);
  943. } else {
  944. // Or we assume each parent folder contains one missing information
  945. if (preg_match('/[\/\\\\]([^\/\\\\]*)[\/\\\\]Season (\d{1,2})[\/\\\\]((E|Ep|Episode)\s?(\d{1,2})[\/\\\\])?/i', $filename, $matches)) {
  946. if ($matches != null) {
  947. $results['tvshow'] = $this->fixSerieName($matches[1]);
  948. $results['tvshow_season'] = $matches[2];
  949. if (isset($matches[5])) {
  950. $results['tvshow_episode'] = $matches[5];
  951. }
  952. }
  953. }
  954. }
  955. }
  956. }
  957. }
  958. if (in_array('movie', $this->gather_types)) {
  959. $pathinfo = pathinfo($filename);
  960. $filetitle = $pathinfo['filename'];
  961. $results['title'] = $this->fixVideoReleaseName($filetitle);
  962. if (!$results['title']) {
  963. // Try to identify the movie information from parent folder
  964. $filetitle = basename($pathinfo['dirname']);
  965. $results['title'] = $this->fixVideoReleaseName($filetitle);
  966. }
  967. }
  968. return $results;
  969. }
  970. private function parseEpisodeName($filetitle)
  971. {
  972. $patterns = array(
  973. '/(.*)s(\d\d)e(\d\d)(\D.*)/i',
  974. '/(.*)s(\d\d)(\D)(.*)/i',
  975. '/(.*)\D(\d{1,2})x(\d\d)(\D)(.*)/i',
  976. '/(.*)\D(\d{1,2})x(\d\d)$/i',
  977. '/(\D*)[\.|\-|_](\d)(\d\d)([\.|\-|_]\D.*)/i',
  978. '/(\D*)(\d)[^0-9](\d\d)(\D.*)/i'
  979. );
  980. $results = array();
  981. for ($i=0;$i<count($patterns);$i++) {
  982. if (preg_match($patterns[$i], $filetitle, $matches)) {
  983. $name = $this->fixSerieName($matches[1]);
  984. if (empty($name)) {
  985. continue;
  986. }
  987. $season = floatval($matches[2]);
  988. if ($season == 0) {
  989. continue;
  990. }
  991. $episode = floatval($matches[3]);
  992. $leftover = $matches[4];
  993. if ($episode == 0) {
  994. // Some malformed string
  995. $leftover = $filetitle;
  996. }
  997. $results['tvshow'] = $name;
  998. $results['tvshow_season'] = $season;
  999. $results['tvshow_episode'] = $episode;
  1000. $results['title'] = $this->fixVideoReleaseName($leftover);
  1001. break;
  1002. }
  1003. }
  1004. return $results;
  1005. }
  1006. private function fixSerieName($name)
  1007. {
  1008. $name = str_replace('_', ' ', $name);
  1009. $name = str_replace('.', ' ', $name);
  1010. $name = str_replace(' ', ' ', $name);
  1011. $name = $this->removeStartingDashesAndSpaces($name);
  1012. $name = $this->removeEndingDashesAndSpaces($name);
  1013. return ucwords($name);
  1014. }
  1015. private function fixVideoReleaseName($name)
  1016. {
  1017. $commonabbr = array(
  1018. 'divx', 'xvid', 'dvdrip', 'hdtv', 'lol', 'axxo', 'repack', 'xor',
  1019. 'pdtv', 'real', 'vtv', 'caph', '2hd', 'proper', 'fqm', 'uncut',
  1020. 'topaz', 'tvt', 'notv', 'fpn', 'fov', 'orenji', '0tv', 'omicron',
  1021. 'dsr', 'ws', 'sys', 'crimson', 'wat', 'hiqt', 'internal', 'brrip',
  1022. 'boheme', 'vost', 'vostfr', 'fastsub', 'addiction'
  1023. );
  1024. for ($i=0; $i<count($commonabbr); $i++) {
  1025. $name = preg_replace('/[\W|_]' . $commonabbr[$i] . '[\W|_](.*)/i', '.', $name);
  1026. }
  1027. while (strpos($name, '..') !== false) {
  1028. $name = preg_replace('/\.\./', '.', $name);
  1029. }
  1030. $name = preg_replace('/\.\w*$/', ' ', $name);
  1031. $name = preg_replace('/\[.*$/', '', $name);
  1032. return $this->fixSerieName($name);
  1033. }
  1034. private function removeStartingDashesAndSpaces($name)
  1035. {
  1036. if (empty($name)) {
  1037. return $name;
  1038. }
  1039. while (strpos($name, ' ') === 0 || strpos($name, '-') === 0) {
  1040. $name = preg_replace('/^ /', '', $name);
  1041. $name = preg_replace('/^-/', '', $name);
  1042. }
  1043. return $name;
  1044. }
  1045. private function removeEndingDashesAndSpaces($name)
  1046. {
  1047. if (empty($name)) {
  1048. return $name;
  1049. }
  1050. while (strrpos($name, ' ') === strlen($name) - 1 || strrpos($name, '-') === strlen($name) - 1) {
  1051. $name = preg_replace('/ $/', '', $name);
  1052. $name = preg_replace('/-$/', '', $name);
  1053. }
  1054. return $name;
  1055. }
  1056. /**
  1057. * set_broken
  1058. *
  1059. * This fills all tag types with Unknown (Broken)
  1060. *
  1061. * @return array Return broken title, album, artist
  1062. */
  1063. public function set_broken()
  1064. {
  1065. /* Pull In the config option */
  1066. $order = AmpConfig::get('tag_order');
  1067. if (!is_array($order)) {
  1068. $order = array($order);
  1069. }
  1070. $key = array_shift($order);
  1071. $broken = array();
  1072. $broken[$key] = array();
  1073. $broken[$key]['title'] = '**BROKEN** ' . $this->filename;
  1074. $broken[$key]['album'] = 'Unknown (Broken)';
  1075. $broken[$key]['artist'] = 'Unknown (Broken)';
  1076. return $broken;
  1077. }
  1078. // set_broken
  1079. /**
  1080. *
  1081. * @param array $data
  1082. * @return array
  1083. * @throws Exception
  1084. */
  1085. private function parseGenres($data)
  1086. {
  1087. // read additional id3v2 delimiters from config
  1088. $delimiters = AmpConfig::get('additional_genre_delimiters');
  1089. if (isset($data) && is_array($data) && count($data) === 1 && isset($delimiters)) {
  1090. $pattern = '~[\s]?(' . $delimiters . ')[\s]?~';
  1091. $genres = preg_split($pattern, reset($data));
  1092. if ($genres === false) {
  1093. throw new Exception('Pattern given in additional_genre_delimiters is not functional. Please ensure is it a valid regex (delimiter ~).');
  1094. }
  1095. $data = $genres;
  1096. }
  1097. return $data;
  1098. }
  1099. } // end class vainfo