PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/class/recommendation.class.php

https://gitlab.com/x33n/ampache
PHP | 362 lines | 262 code | 50 blank | 50 comment | 47 complexity | 847942510af08299a58f411df3d61f59 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. class Recommendation
  23. {
  24. /**
  25. * Constructor
  26. * Not on my watch, boyo.
  27. */
  28. private function __construct()
  29. {
  30. return false;
  31. } //constructor
  32. /**
  33. * get_lastfm_results
  34. * Runs a last.fm query and returns the parsed results
  35. */
  36. public static function get_lastfm_results($method, $query)
  37. {
  38. $api_key = AmpConfig::get('lastfm_api_key');
  39. $api_base = "http://ws.audioscrobbler.com/2.0/?method=";
  40. $url = $api_base . $method . '&api_key=' . $api_key . '&' . $query;
  41. return self::query_lastfm($url);
  42. }
  43. public static function query_lastfm($url)
  44. {
  45. debug_event('Recommendation', 'search url : ' . $url, 5);
  46. $request = Requests::get($url, array(), Core::requests_options());
  47. $content = $request->body;
  48. return simplexml_load_string($content);
  49. }
  50. public static function album_search($artist, $album)
  51. {
  52. $url = 'http://ws.audioscrobbler.com/1.0/album/' . urlencode($artist) . '/' . urlencode($album) . '/info.xml';
  53. return self::query_lastfm($url);
  54. }
  55. /**
  56. * gc
  57. *
  58. * This cleans out old recommendations cache
  59. */
  60. public static function gc()
  61. {
  62. Dba::write('DELETE FROM `recommendation` WHERE `last_update` < ?', array((time() - 604800)));
  63. }
  64. protected static function get_recommendation_cache($type, $id, $get_items = false)
  65. {
  66. self::gc();
  67. $sql = "SELECT `id`, `last_update` FROM `recommendation` WHERE `object_type` = ? AND `object_id` = ?";
  68. $db_results = Dba::read($sql, array($type, $id));
  69. if ($cache = Dba::fetch_assoc($db_results)) {
  70. if ($get_items) {
  71. $cache['items'] = array();
  72. $sql = "SELECT `recommendation_id`, `name`, `rel`, `mbid` FROM `recommendation_item` WHERE `recommendation` = ?";
  73. $db_results = Dba::read($sql, array($cache['id']));
  74. while ($results = Dba::fetch_assoc($db_results)) {
  75. $cache['items'][] = array(
  76. 'id' => $results['recommendation_id'],
  77. 'name' => $results['name'],
  78. 'rel' => $results['rel'],
  79. 'mbid' => $results['mbid'],
  80. );
  81. }
  82. }
  83. }
  84. return $cache;
  85. }
  86. protected static function delete_recommendation_cache($type, $id)
  87. {
  88. $cache = self::get_recommendation_cache($type, $id);
  89. if ($cache['id']) {
  90. Dba::write('DELETE FROM `recommendation_item` WHERE `recommendation` = ?', array($cache['id']));
  91. Dba::write('DELETE FROM `recommendation` WHERE `id` = ?', array($cache['id']));
  92. }
  93. }
  94. protected static function update_recommendation_cache($type, $id, $recommendations)
  95. {
  96. self::delete_recommendation_cache($type, $id);
  97. $sql = "INSERT INTO `recommendation` (`object_type`, `object_id`, `last_update`) VALUES (?, ?, ?)";
  98. Dba::write($sql, array($type, $id, time()));
  99. $insertid = Dba::insert_id();
  100. foreach ($recommendations as $recommendation) {
  101. $sql = "INSERT INTO `recommendation_item` (`recommendation`, `recommendation_id`, `name`, `rel`, `mbid`) VALUES (?, ?, ?, ?, ?)";
  102. Dba::write($sql, array($insertid, $recommendation['id'], $recommendation['name'], $recommendation['rel'], $recommendation['mbid']));
  103. }
  104. }
  105. /**
  106. * get_songs_like
  107. * Returns a list of similar songs
  108. */
  109. public static function get_songs_like($song_id, $limit = 5, $local_only = true)
  110. {
  111. $song = new Song($song_id);
  112. if (isset($song->mbid)) {
  113. $query = 'mbid=' . rawurlencode($song->mbid);
  114. } else {
  115. $query = 'track=' . rawurlencode($song->title);
  116. }
  117. $cache = self::get_recommendation_cache('song', $song_id, true);
  118. if (!$cache['id']) {
  119. $similars = array();
  120. $xml = self::get_lastfm_results('track.getsimilar', $query);
  121. if ($xml->similartracks) {
  122. foreach ($xml->similartracks->children() as $child) {
  123. $name = $child->name;
  124. $local_id = null;
  125. $artist_name = $child->artist->name;
  126. $s_artist_name = Catalog::trim_prefix($artist_name);
  127. $sql = "SELECT `song`.`id` FROM `song` " .
  128. "LEFT JOIN `artist` ON " .
  129. "`song`.`artist`=`artist`.`id` ";
  130. if (AmpConfig::get('catalog_disable')) {
  131. $sql .= "LEFT JOIN `catalog` ON `song`.`catalog` = `catalog`.`id` ";
  132. }
  133. $sql .= "WHERE `song`.`title` = ? " .
  134. "AND `artist`.`name` = ? ";
  135. if (AmpConfig::get('catalog_disable')) {
  136. $sql .= "AND `catalog`.`enabled` = '1'";
  137. }
  138. $db_result = Dba::read($sql, array($name, $s_artist_name['string']));
  139. if ($result = Dba::fetch_assoc($db_result)) {
  140. $local_id = $result['id'];
  141. }
  142. if (is_null($local_id)) {
  143. debug_event('Recommendation', "$name did not match any local song", 5);
  144. $similars[] = array(
  145. 'id' => null,
  146. 'name' => $name,
  147. 'rel' => $artist_name
  148. );
  149. } else {
  150. debug_event('Recommendation', "$name matched local song $local_id", 5);
  151. $similars[] = array(
  152. 'id' => $local_id,
  153. 'name' => $name
  154. );
  155. }
  156. }
  157. if (count($similars) > 0) {
  158. self::update_recommendation_cache('song', $song_id, $similars);
  159. }
  160. }
  161. }
  162. if (!isset($similars) || count($similars) == 0) {
  163. $similars = $cache['items'];
  164. }
  165. if ($similars) {
  166. $results = array();
  167. foreach ($similars as $similar) {
  168. if (!$local_only || !is_null($similar['id'])) {
  169. $results[] = $similar;
  170. }
  171. if ($limit && count($results) >= $limit) {
  172. break;
  173. }
  174. }
  175. }
  176. if (isset($results)) {
  177. return $results;
  178. }
  179. return false;
  180. }
  181. /**
  182. * get_artists_like
  183. * Returns a list of similar artists
  184. */
  185. public static function get_artists_like($artist_id, $limit = 10, $local_only = true)
  186. {
  187. $artist = new Artist($artist_id);
  188. $cache = self::get_recommendation_cache('artist', $artist_id, true);
  189. if (!$cache['id']) {
  190. $similars = array();
  191. $query = 'artist=' . rawurlencode($artist->name);
  192. $xml = self::get_lastfm_results('artist.getsimilar', $query);
  193. foreach ($xml->similarartists->children() as $child) {
  194. $name = $child->name;
  195. $mbid = (string) $child->mbid;
  196. $local_id = null;
  197. // First we check by MBID
  198. if ($mbid) {
  199. $sql = "SELECT `artist`.`id` FROM `artist` WHERE `mbid` = ?";
  200. if (AmpConfig::get('catalog_disable')) {
  201. $sql .= " AND " . Catalog::get_enable_filter('artist', '`artist`.`id`');
  202. }
  203. $db_result = Dba::read($sql, array($mbid));
  204. if ($result = Dba::fetch_assoc($db_result)) {
  205. $local_id = $result['id'];
  206. }
  207. }
  208. // Then we fall back to the less likely to work exact
  209. // name match
  210. if (is_null($local_id)) {
  211. $searchname = Catalog::trim_prefix($name);
  212. $searchname = Dba::escape($searchname['string']);
  213. $sql = "SELECT `artist`.`id` FROM `artist` WHERE `name` = ?";
  214. if (AmpConfig::get('catalog_disable')) {
  215. $sql .= " AND " . Catalog::get_enable_filter('artist', '`artist`.`id`');
  216. }
  217. $db_result = Dba::read($sql, array($searchname));
  218. if ($result = Dba::fetch_assoc($db_result)) {
  219. $local_id = $result['id'];
  220. }
  221. }
  222. // Then we give up
  223. if (is_null($local_id)) {
  224. debug_event('Recommendation', "$name did not match any local artist", 5);
  225. $similars[] = array(
  226. 'id' => null,
  227. 'name' => $name,
  228. 'mbid' => $mbid
  229. );
  230. } else {
  231. debug_event('Recommendation', "$name matched local artist " . $local_id, 5);
  232. $similars[] = array(
  233. 'id' => $local_id,
  234. 'name' => $name
  235. );
  236. }
  237. }
  238. if (count($similars) > 0) {
  239. self::update_recommendation_cache('artist', $artist_id, $similars);
  240. }
  241. }
  242. if (!isset($similars) || count($similars) == 0) {
  243. $similars = $cache['items'];
  244. }
  245. if ($similars) {
  246. $results = array();
  247. foreach ($similars as $similar) {
  248. if (!$local_only || !is_null($similar['id'])) {
  249. $results[] = $similar;
  250. }
  251. if ($limit && count($results) >= $limit) {
  252. break;
  253. }
  254. }
  255. }
  256. if (isset($results)) {
  257. return $results;
  258. }
  259. return false;
  260. } // get_artists_like
  261. /**
  262. * get_artist_info
  263. * Returns artist information
  264. */
  265. public static function get_artist_info($artist_id, $fullname='')
  266. {
  267. $artist = null;
  268. if ($artist_id) {
  269. $artist = new Artist($artist_id);
  270. $artist->format();
  271. $fullname = $artist->f_full_name;
  272. // Data newer than 6 months, use it
  273. if (($artist->last_update + 15768000) > time()) {
  274. $results = array();
  275. $results['id'] = $artist_id;
  276. $results['summary'] = $artist->summary;
  277. $results['placeformed'] = $artist->placeformed;
  278. $results['yearformed'] = $artist->yearformed;
  279. $results['largephoto'] = Art::url($artist->id, 'artist');
  280. $results['smallphoto'] = $results['largephoto']; // TODO: Change to thumb size?
  281. $results['mediumphoto'] = $results['largephoto']; // TODO: Change to thumb size?
  282. $results['megaphoto'] = $results['largephoto'];
  283. return $results;
  284. }
  285. }
  286. $query = 'artist=' . rawurlencode($fullname);
  287. $xml = self::get_lastfm_results('artist.getinfo', $query);
  288. $results = array();
  289. $results['summary'] = strip_tags(preg_replace("#<a href=([^<]*)Last\.fm</a>.#", "", (string) $xml->artist->bio->summary));
  290. $results['placeformed'] = (string) $xml->artist->bio->placeformed;
  291. $results['yearformed'] = (string) $xml->artist->bio->yearformed;
  292. $results['smallphoto'] = $xml->artist->image[0];
  293. $results['mediumphoto'] = $xml->artist->image[1];
  294. $results['largephoto'] = $xml->artist->image[2];
  295. $results['megaphoto'] = $xml->artist->image[4];
  296. if ($artist) {
  297. $results['id'] = $artist->id;
  298. if (!empty($results['summary']) || !empty($results['megaphoto'])) {
  299. $artist->update_artist_info($results['summary'], $results['placeformed'], $results['yearformed']);
  300. $image = Art::get_from_source(array('url' => $results['megaphoto']), 'artist');
  301. $rurl = pathinfo($results['megaphoto']);
  302. $mime = 'image/' . $rurl['extension'];
  303. $art = new Art($artist->id, 'artist');
  304. $art->reset();
  305. $art->insert($image, $mime);
  306. $results['largephoto'] = Art::url($artist->id, 'artist');
  307. $results['megaphoto'] = $results['largephoto'];
  308. }
  309. }
  310. return $results;
  311. } // get_artist_info
  312. } // end of recommendation class