* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ define("CACHE_SQLITE_FILE", "./data/cache.db"); define("CACHE_EXPIRY_SECONDS", 30 * 24 * 60 * 60); // 30 days... function fetchfile($URL, &$effectiveURL) { $c = curl_init(); curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); curl_setopt($c, CURLOPT_URL, $URL); curl_setopt($c, CURLOPT_ENCODING, "gzip"); curl_setopt($c, CURLOPT_FOLLOWLOCATION, true); curl_setopt($c, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0"); // YEP. $contents = curl_exec($c); $effectiveURL = curl_getinfo($c, CURLINFO_EFFECTIVE_URL); curl_close($c); if ($contents) return $contents; else return FALSE; } class CoverArtResult { var $title; var $detailsURL; var $thumbnailImageURL; var $imageURL; } abstract class AbstractCoverArtProvider { abstract public function getCoverArtList($artist, $album, $limit = 0); public function getCoverArtImage($artist, $album) { $coverArtList = $this->getCoverArtList($artist, $album, 1); $result = array(); $this->tryDownloadImage($result, $coverArtList->items, 'image/jpeg'); return $result; } public function name() { return get_class($this); } protected function getHTMLDom($url) { $startTime = microtime(true); $dom = new DomDocument(); $effectiveURL = $url; $html = fetchfile($url, $effectiveURL); $htmlLoaded = @$dom->loadHTML($html); $endTime = microtime(true); $fetchTime = $endTime - $startTime; $result->dom = $dom; //$result->html = $html; $result->details['fetchTime'] = $fetchTime; $result->details['url'] = $url; $result->details['effectiveURL'] = $effectiveURL; $result->loaded = $htmlLoaded; return $result; } protected function getDOMNodesByXPath($dom, $xpath, $limit = 0, $xpathStopCondition = "") { $stopEval = false; $result->items = array(); $result->details['xpath'] = $xpath; if (!empty($xpathStopCondition)) $result->details['xpathStopCondition'] = $xpathStopCondition; if (isset($dom->dom)) $domDoc = $dom->dom; else if (is_subclass_of($dom, "DOMNode")) { $domDoc = $dom->ownerDocument; $domNode = $dom; } else $domDoc = NULL; if (!is_null($domDoc)) { $xp = new DomXPath($domDoc); $stopEval = false; if (!empty($xpathStopCondition)) { if (isset($domNode)) $items = $xp->evaluate($xpathStopCondition, $domNode); else $items = $xp->evaluate($xpathStopCondition); $stopEval = $items->length > 0; $result->details['xpathStopConditionMatched'] = $stopEval; } if (!$stopEval) { $startTime = microtime(true); if (isset($domNode)) $items = $xp->evaluate($xpath, $domNode); else $items = $xp->evaluate($xpath); if (is_object($items)) { $i = 0; foreach ($items as $item) { $result->items[] = $item; ++$i; if ($limit > 0 && $i == $limit) break; } } else if (!empty($items)) $result->items[] = $items; $count = count($result->items); $endTime = microtime(true); $evalTime = $endTime - $startTime; $result->details['evalTime'] = $evalTime; } } return $result; } protected function getListByXPath($dom, $xpath, $limit = 0, $xpathStopCondition = "", $invalidItemFilter = "") { $result = $this->getDOMNodesByXPath($dom, $xpath, $limit, $xpathStopCondition); $list = array(); foreach ($result->items as $item) { if (is_object($item)) $item = $item->nodeValue; if (!empty($item) && $item != $invalidItemFilter) $list[] = $item; } $result->items = $list; return $result; } protected function tryDownloadImage(&$result, $imageURLs, $mimetype = 'image/jpeg') { $result['success'] = false; if (count($imageURLs) > 0) { $imageData = false; $i = 0; while ($imageData === false && $i < count($imageURLs)) { $imageData = fetchfile($imageURLs[$i]); ++$i; } if ($imageData) { $result['imagedata'] = $imageData; $result['mimetype'] = $mimetype; $result['success'] = true; } } } } class CoverArtProviderAmazon extends AbstractCoverArtProvider { private $topLevelDomain; function __construct($topLevelDomain = "com") { $this->topLevelDomain = $topLevelDomain; } public function name() { return AbstractCoverArtProvider::name() . "_" . $this->topLevelDomain; } public function getCoverArtList($artist, $album, $limit = 0) { $list->results = array(); $dom = $this->getHTMLDom('http://www.amazon.' . $this->topLevelDomain . '/gp/search/?search-alias=popular&unfiltered=1&sort=salesrank&field-keywords=&field-artist=' . urlencode($artist) . '&field-title=' . urlencode($album)); $list->details[] = $dom->details; $resultNodes = $this->getDOMNodesByXPath( $dom, '//div[@id="atfResults" or @id="btfResults"]//li[starts-with(@id, "result_") and contains(.//img/@class, "s-access-image") and not(contains(.//img/@src, "no-img-"))]', $limit ); $list->details[] = $resultNodes; foreach ($resultNodes->items as $resultNode) { $titleList = $this->getListByXPath($resultNode, './/a[contains(@class, "s-access-detail-page")]/@title'); $list->details[] = $titleList; $detailsList = $this->getListByXPath($resultNode, './/a[contains(@class, "s-access-detail-page")]/@href'); $list->details[] = $detailsList; $thumbnailImageList = $this->getListByXPath($resultNode, './/img[contains(@class, "s-access-image")]/@src'); $list->details[] = $thumbnailImageList; $coverArtItem = new CoverArtResult(); $coverArtItem->title = $titleList->items[0]; $coverArtItem->detailsURL = $detailsList->items[0]; $coverArtItem->thumbnailImageURL = $thumbnailImageList->items[0]; $coverArtItem->imageURL = preg_replace("/\._.*?\.jpg/", ".jpg", $coverArtItem->thumbnailImageURL); // Replace ._AA160_.jpg with .jpg if ($coverArtItem->thumbnailImageURL != "" && $coverArtItem->imageURL != "") $list->results[] = $coverArtItem; } return $list; } } class CoverArtProviderGoogleImages extends AbstractCoverArtProvider { private $topLevelDomain; function __construct($topLevelDomain = "com") { $this->topLevelDomain = $topLevelDomain; } public function name() { return AbstractCoverArtProvider::name() . "_" . $this->topLevelDomain; } public function getCoverArtList($artist, $album, $limit = 0) { $list->results = array(); // tbm=isch -> We want image search // tbs=isz:l -> We want large images $dom = $this->getHTMLDom('http://www.google.' . $this->topLevelDomain . '/search?tbm=isch&tbs=isz:l&q=Album+%22' . urlencode($artist) . '%22+%22' . urlencode($album) . '%22'); $list->details[] = $dom->details; $resultNodes = $this->getDOMNodesByXPath($dom, '//div[@id="rg_s"]//div[contains(@class,"rg_di")]', $limit); $list->details[] = $resultNodes; foreach ($resultNodes->items as $resultNode) { $metaList = $this->getListByXPath($resultNode, './/div[contains(@class,"rg_meta")]/text()'); $list->details[] = $metaList; $coverArtItem = new CoverArtResult(); // Get metadata from rg_meta JSON payload... $meta = json_decode($metaList->items[0]); $coverArtItem->title = $meta->pt; $coverArtItem->detailsURL = $meta->ru; $coverArtItem->imageURL = $meta->ou; $coverArtItem->thumbnailImageURL = $meta->tu; if ($coverArtItem->thumbnailImageURL != "" && $coverArtItem->imageURL != "") $list->results[] = $coverArtItem; } return $list; } } class CoverArtProviderLastFM extends AbstractCoverArtProvider { public function getCoverArtList($artist, $album, $limit = 0) { $list->results = array(); $detailURL = 'http://www.lastfm.com/music/' . urlencode($artist) . '/' . urlencode($album); $dom = $this->getHTMLDom($detailURL); $list->details[] = $dom; $resultNodes = $this->getDOMNodesByXPath($dom, '/html[.//img[@class="cover-art"]]', $limit); $list->details[] = $resultNodes; foreach ($resultNodes->items as $resultNode) { $imageList = $this->getListByXPath($resultNode, '//img[@class="cover-art"]/@src'); $list->details[] = $imageList; $titleList = $this->getListByXPath($resultNode, '//img[@class="cover-art"]/@alt'); $list->details[] = $titleList; $thumbnailImageList = $imageList; $coverArtItem = new CoverArtResult(); $coverArtItem->title = utf8_decode($titleList->items[0]); $coverArtItem->detailsURL = $detailURL; $coverArtItem->thumbnailImageURL = $thumbnailImageList->items[0]; $coverArtItem->imageURL = str_replace("/u/174s/", "/u/", $imageList->items[0]); if ($coverArtItem->thumbnailImageURL != "" && $coverArtItem->imageURL != "" && stristr($coverArtItem->thumbnailImageURL, "c6f59c1e5e7240a4c0d427abd71f3dbb") === FALSE && // Placeholder image, probably hash stristr($coverArtItem->imageURL, "c6f59c1e5e7240a4c0d427abd71f3dbb") === FALSE && stristr($coverArtItem->thumbnailImageURL, "noimage") === FALSE && stristr($coverArtItem->imageURL, "noimage") === FALSE) $list->results[] = $coverArtItem; } return $list; } } class CoverArtProviderCache extends AbstractCoverArtProvider { private $coverArtProviders; private $coverArtProviderQueryLimit; private $dbh; private $selectStmt; private $insertSearchStmt; private $insertResultStmt; function __construct($coverArtProviders = NULL, $coverArtProviderQueryLimit = 0) { if (is_null($coverArtProviders)) { global $globalCoverArtProviders; $coverArtProviders = $globalCoverArtProviders; } $this->coverArtProviders = $coverArtProviders; $this->coverArtProviderQueryLimit = $coverArtProviderQueryLimit; $createDB = !file_exists(CACHE_SQLITE_FILE); $this->dbh = new PDO('sqlite:' . CACHE_SQLITE_FILE); if ($createDB) { $this->dbh->exec(' CREATE TABLE "searches" ( "search_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "provider_name" TEXT NOT NULL COLLATE NOCASE, "artist" TEXT NOT NULL COLLATE NOCASE, "album" TEXT NOT NULL COLLATE NOCASE, "timestamp" INTEGER NOT NULL ); CREATE TABLE "searches_results" ( "result_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "search_id" INTEGER, "index" INTEGER NOT NULL, "title" TEXT NOT NULL, "details_url" TEXT NOT NULL, "thumbnail_image_url" TEXT NOT NULL, "image_url" TEXT NOT NULL ); CREATE INDEX "searches_provider_name" on searches (provider_name ASC); CREATE INDEX "searches_artist" on searches (artist ASC); CREATE INDEX "searches_album" on searches (album ASC); CREATE INDEX "searches_results_search_id" on searches_results (search_id ASC); CREATE TRIGGER delete_searches_results DELETE ON searches BEGIN DELETE FROM searches_results WHERE search_id = OLD.search_id; END; '); } $this->selectStmt = $this->dbh->prepare( "SELECT * FROM searches " . "LEFT JOIN searches_results AS sr ON sr.search_id = searches.search_id " . "WHERE provider_name = ? AND artist = ? AND album = ? ORDER BY `index`;" ); if (!$this->selectStmt) { echo "\nPDO::errorInfo():\n"; print_r($this->dbh->errorInfo()); } $this->insertSearchStmt = $this->dbh->prepare( "INSERT INTO searches (search_id, provider_name, artist, album, timestamp) " . "VALUES (NULL, ?, ?, ?, ?);" ); if (!$this->insertSearchStmt) { echo "\nPDO::errorInfo():\n"; print_r($this->dbh->errorInfo()); } $this->insertResultStmt = $this->dbh->prepare( "INSERT INTO searches_results (result_id, search_id, `index`, title, details_url, thumbnail_image_url, image_url) " . "VALUES (NULL, ?, ?, ?, ?, ?, ?);" ); if (!$this->insertResultStmt) { echo "\nPDO::errorInfo():\n"; print_r($this->dbh->errorInfo()); } } public function getCoverArtList($artist, $album, $limit = 0) { $listLimit = $limit; $list->results = array(); $coverArtProviderQueried = 0; foreach ($this->coverArtProviders as $provider) { $success = $this->selectStmt->execute(array($provider->name(), $artist, $album)); $rows = $this->selectStmt->fetchAll(); $resultsValid = false; if ($success && count($rows) > 0) { $resultsValid = $rows[0]["timestamp"] > time() - CACHE_EXPIRY_SECONDS; if (!$resultsValid) { //echo "Cached results expired, removing " . $rows[0][0]; $this->dbh->exec("DELETE FROM searches WHERE search_id = " . $rows[0][0] . ";"); } } if ($resultsValid) { //echo "Something was found in the cache..."; $coverArtList->results = array(); foreach ($rows as $row) { if (is_null($row["image_url"])) break; $coverArtItem = new CoverArtResult(); $coverArtItem->title = $row["title"]; $coverArtItem->detailsURL = $row["details_url"]; $coverArtItem->thumbnailImageURL = $row["thumbnail_image_url"]; $coverArtItem->imageURL = $row["image_url"]; if ($coverArtItem->thumbnailImageURL != "" && $coverArtItem->imageURL != "") $coverArtList->results[] = $coverArtItem; if ($limit > 0 && count($coverArtList->results) == $listLimit) break; } } else { //echo "Nothing found in the cache..."; $coverArtList = $provider->getCoverArtList($artist, $album, $listLimit); $this->dbh->beginTransaction(); if ($this->insertSearchStmt->execute(array($provider->name(), $artist, $album, time()))) { $searchID = $this->dbh->lastInsertId(); //echo "Search inserted: " . $searchID; foreach ($coverArtList->results as $index => $result) { $this->insertResultStmt->execute(array( $searchID, $index, $result->title, $result->detailsURL, $result->thumbnailImageURL, $result->imageURL )); } } $this->dbh->commit(); } $list->results = array_merge($list->results, $coverArtList->results); if ($limit > 0) { $listLimit = max(0, $listLimit - count($coverArtList->results)); if ($listLimit == 0) break; } if (count($coverArtList->results) > 0) ++$coverArtProviderQueried; if ($this->coverArtProviderQueryLimit > 0 && count($coverArtList->results) > 0 && $coverArtProviderQueried == $this->coverArtProviderQueryLimit) break; } return $list; } } $globalCoverArtProviders = array( new CoverArtProviderLastFM(), new CoverArtProviderGoogleImages("de"), new CoverArtProviderGoogleImages("com"), new CoverArtProviderAmazon("de"), new CoverArtProviderAmazon("com") ); function getCoverArtResults($artist, $album, $limit = 0, $coverArtProviders = NULL) { $results = array(); $listLimit = $limit; if (is_null($coverArtProviders)) { global $globalCoverArtProviders; $coverArtProviders = $globalCoverArtProviders; } foreach ($coverArtProviders as $provider) { $coverArtList = $provider->getCoverArtList($artist, $album, $listLimit); $results = array_merge($results, $coverArtList->results); if ($limit > 0) { $listLimit = max(0, $listLimit - count($coverArtList->results)); if ($listLimit == 0) break; } } return $results; } ?>