{$param} = $params[$param]; } else { $this->{$param} = null; } } // We need to be able to send baseimage as '' for it all to work with the collection, // And X-AlbumImage defaults to null for all kinds of reasons, probably. if (array_key_exists('baseimage', $params)) { $this->baseimage = $params['baseimage']; } else { $this->baseimage = 'No-Image'; } if (array_key_exists("ufile", $_FILES)) { $this->file = $_FILES['ufile']['name']; } if ($this->mbid !== null) { if (preg_match('/\d+/', $this->mbid) && !preg_match('/-/', $this->mbid)) { logger::log("ALBUMIMAGE", " Supplied MBID of ".$mbid." looks more like a Discogs ID"); $this->mbid = null; } } if ($prefs['player_backend'] == 'mopidy') { $this->albumpath = urldecode($this->albumpath); } $this->image_downloaded = false; if ($this->baseimage != 'No-Image') { $this->images = $this->image_paths_from_base_image($params['baseimage']); $this->key = $this->make_image_key(); } else if ($this->key !== null) { $this->image_info_from_database(); } else { $this->images = $this->image_info_from_album_info(); } } public function get_image_key() { return $this->key; } private function image_exists($image) { logger::trace("ALBUMIMAGE", "Checking for existence of file ".$image); return file_exists($image); } public function get_image_if_exists() { foreach (array('png', 'svg', 'jpg') as $ext) { $this->change_file_extension($ext); if ($this->image_exists($this->images['small'])) { return $this->images['small']; } } if ($this->artist == 'PLAYLIST' && $this->album == 'Discover Weekly (by spotify)') { $this->images['small'] = 'newimages/discoverweekly.jpg'; $this->images['medium'] = 'newimages/discoverweekly.jpg'; $this->images['asdownloaded'] = 'newimages/discoverweekly.jpg'; return 'newimages/discoverweekly.jpg'; } return null; } public function get_images() { return $this->images; } private function check_if_image_already_downloaded() { $checkimages = $this->image_info_from_album_info(); if ($this->image_exists($checkimages['small'])) { logger::trace("ALBUMIMAGE", " .. File exists"); $this->images = $checkimages; return true; } else { return false; } } public function is_collection_image() { return preg_match('#albumart/small/#', $this->images['small']); } public function check_image($domain, $type, $in_playlist = false) { global $doing_search; // If there's no image, see if we can set a default // Note we don't set defaults for streams because coverscaper handles those // so it can set them in the playlist even when auto art download is off $disc_checked = false; if ($this->images['small'] == '' || $this->images['small'] === null) { if ($this->artist == 'STREAM') { // Stream images may not be in the database // BUT they may be present anyway, if a stream was added eg from a playlist // of streams and coverscraper found one. So check, otherwise coverscraper // will search for it every time the playlist repopulates. if ($this->check_if_image_already_downloaded()) { return true; } $disc_checked = true; } // Checking if the file already exists on disc doesn't help at all // when we're building a collection (and only slows things down). // If the album is already in the collection it'll have an image (or not) and // this will be in the database. The collection update will not change it // if this returns no image because we use best_value() if ($in_playlist) { if (!$disc_checked && $this->check_if_image_already_downloaded()) { // Image may have already been downloaded if we've added the album // to the Current Playlist from search results. } else { // Different defaults for the Playlist, we'd like to be able // to download images and the information we get in the playlist // (from Mopidy anyway, which is where these matter), is more complete // than what comes from search results which often lack any sort // of useful information. switch ($domain) { case 'bassdrive': case 'internetarchive': case 'oe1': case 'soundcloud': case 'youtube': $this->images = $this->image_paths_from_base_image('newimages/'.$domain.'-logo.svg'); break; } } return true; } if ($doing_search) { if (!$disc_checked && $this->check_if_image_already_downloaded()) { // We may have searched for it before return true; } } switch ($domain) { case 'bassdrive': case 'dirble': case 'internetarchive': case 'oe1': case 'podcast': case 'radio-de': case 'soundcloud': case 'tunein': case 'youtube': $this->images = $this->image_paths_from_base_image('newimages/'.$domain.'-logo.svg'); break; } } } private function image_paths_from_base_image($image) { $images = array( 'small' => $image, 'medium' => preg_replace('#albumart/small/#', 'albumart/medium/', $image), 'asdownloaded' => preg_replace('#albumart/small/#', 'albumart/asdownloaded/', $image) ); if (substr($image, 0, 14) == 'getRemoteImage') { array_walk($images, function(&$v, $k) { $v .= '&rompr_resize_size='.$k; }); } return $images; } protected function change_file_extension($new) { foreach ($this->images as $size => $path) { $p = pathinfo($path); $this->images[$size] = $p['dirname'].'/'.$p['filename'].'.'.$new; } } private function image_info_from_database() { $this->basepath = 'albumart/'; $info = get_imagesearch_info($this->key); foreach ($info as $k => $v) { $this->{$k} = $v; } $smallimage = $this->basepath.'small/'.$this->key.'.jpg'; $this->images = $this->image_paths_from_base_image($smallimage); } protected function image_info_from_album_info() { switch ($this->artist) { case 'PLAYLIST': $this->key = $this->make_image_key(); $this->basepath = 'prefs/plimages/'.$this->key.'/albumart/'; break; case 'STREAM': $this->key = $this->make_image_key(); $this->basepath = 'prefs/userstreams/'.$this->key.'/albumart/'; break; case 'PODCAST': $this->key = $this->make_image_key(); $this->basepath = 'prefs/podcasts/'.$this->albumpath.'/albumart/'; break; default: $this->key = $this->make_image_key(); $this->basepath = 'albumart/'; break; } $smallimage = $this->basepath.'small/'.$this->key.'.jpg'; return $this->image_paths_from_base_image($smallimage); } private function make_image_key() { $key = strtolower($this->artist.$this->album); return md5($key); } public function html_for_image($obj, $imageclass, $size) { $extra = (array_key_exists('userplaylist', $obj)) ? 'plimage '.$imageclass : $imageclass; if (!$this->images['small'] && $obj['Searched'] != 1) { return ''."\n"; } else if (!$this->images['small'] && $obj['Searched'] == 1) { return ''."\n"; } else { return ''."\n"; } } } class albumImage extends baseAlbumImage { public function set_source($src) { $this->source = $src; } public function has_source() { if ($this->source === null && $this->file === null && $this->base64data === null) { return false; } else { return true; } } public function download_image() { if (!$this->has_source()) { return false; } $retval = false; if ($this->source) { $retval = $this->download_remote_file(); } else if ($this->file) { $retval = get_user_file($this->file, $this->key, $_FILES['ufile']['tmp_name']); } else if ($this->base64data) { $retval = $this->save_base64_data(); } if ($retval !== false) { $this->image_downloaded = true; $retval = $this->saveImage($retval); } return $retval; } public function update_image_database() { switch ($this->artist) { case 'PLAYLIST'; break; case 'STREAM': if ($this->image_downloaded) { update_stream_image($this->album, $this->images['small']); } break; case 'PODCAST': if ($this->image_downloaded) { update_podcast_image($this->albumpath, $this->images['small']); } break; default: update_image_db($this->key, $this->image_downloaded, $this->images['small']); break; } } public function set_default() { if ($this->artist == "STREAM") { // Set a default image for streams when we are doing an album art download // otherwise they get searched for on every refresh of the playlist if they're // not in the database and nothing gets found the first time. $this->source = 'newimages/broadcast.svg'; return $this->download_image(); } return false; } public function get_artist_for_search() { switch ($this->artist) { case 'PLAYLIST': case 'STREAM': return ''; break; case 'PODCAST': case 'Podcasts': return 'Podcast'; break; default: return $this->artist; break; } } public function change_name($new_name) { switch ($this->artist) { case 'PLAYLIST': logger::log("FUCKINGHELL", "Playlist name changing from ".$this->album." to ".$new_name); if (file_exists($this->images['small'])) { $oldbasepath = dirname($this->basepath); $oldkey = $this->key; $this->album = $new_name; $this->images = $this->image_info_from_album_info(); $newbasepath = dirname($this->basepath); logger::log("ALBUMIMAGE", "Renaming Playlist Image from ".$oldbasepath." to ".$newbasepath); rename($oldbasepath, $newbasepath); foreach ($this->images as $image) { $oldimage = dirname($image).'/'.$oldkey.'.jpg'; rename($oldimage, $image); } } break; } } private function saveImage($download_file) { $imagehandler = new imageHandler($download_file); switch ($imagehandler->checkImage()) { // Include the MIME types here in case we're using ImageMagick after an error // was thrown in gdImage case IMAGETYPE_PNG: case 'image/png': $this->change_file_extension('png'); break; case IMAGETYPE_SVG: case 'image/svg+xml': $this->change_file_extension('svg'); break; } foreach ($this->images as $image) { $dir = dirname($image); $size = basename($dir); if (file_exists($image)) { unlink($image); } if (!is_dir($dir)) { mkdir($dir, 0755, true); } logger::log("ALBUMIMAGE", " Creating file ".$image); switch ($size) { case 'small': $imagehandler->resizeToWidth(100); $imagehandler->save($image, 75); break; case 'smallish': $imagehandler->resizeToWidth(260); $imagehandler->save($image, 70); break; case 'medium': $imagehandler->resizeToWidth(400); $imagehandler->save($image, 70); break; case 'asdownloaded': $imagehandler->reset(); $imagehandler->save($image, 90); break; } } unlink($download_file); $imagehandler->destroy(); return $this->images; } private function download_remote_file() { $download_file = 'prefs/temp/'.$this->key; $retval = $download_file; if (preg_match('/^https*:/', $this->source) || preg_match('/^getRemoteImage.php/', $this->source)) { $d = new url_downloader(array('url' => $this->source)); if ($d->get_data_to_file($download_file, true)) { $content_type = $d->get_content_type(); if (substr($content_type,0,5) != 'image' && $content_type != 'application/octet-stream') { logger::warn("ALBUMIMAGE", " .. Content type is ".$content_type." - not an image file! ".$this->source); $retval = false; } } else { $retval = false; } } else { logger::log("ALBUMIMAGE", " .. Copying apparent local file"); if (!copy($this->source, $download_file)) { logger::fail("ALBUMIMAGE", " .. File Copy Failed"); $retval = false; } } return $retval; } private function save_base64_data() { logger::log("ALBUMIMAGE", " Saving base64 data"); $download_file = 'prefs/temp/'.$this->key; create_image_from_base64($this->base64data, $download_file); return $download_file; } } function create_image_from_base64($base64, $download_file) { $image = explode('base64,',$base64); file_put_contents($download_file, base64_decode($image[1])); } function artist_for_image($type, $artist) { switch ($type) { case 'stream': $artistforimage = 'STREAM'; break; default: $artistforimage = $artist; break; } return $artistforimage; } class imageHandler { private $filename; private $image; public function __construct($filename) { if (extension_loaded('gd')) { $this->image = new gdImage($filename); if ($this->image->checkImage() === false) { logger::log('IMAGEHANDLER', 'Switching to ImageMagick'); $this->image = new imageMagickImage($filename); } } else { $this->image = new imageMagickImage($filename); } } public function checkImage() { return $this->image->checkImage(); } public function reset() { $this->image->reset(); } public function save($filename, $compression = 75) { $this->image->save($filename, $compression); } public function outputResizedFile($size) { $this->image->outputResizedFile($size); } public function resizeToWidth($width) { $this->image->resizeToWidth($width); } public function get_image_dimensions() { return $this->image->get_image_dimensions(); } public function destroy() { $this->image->destroy(); } } class imageMagickImage { private $filename; private $convert_path; private $resize_to = 0; private $image_type; public function __construct($filename) { $this->filename = $filename; $this->convert_path = find_executable('convert'); $this->image_type = mime_content_type($this->filename); logger::log('IMAGEMAGICK', 'Image Type is "'.$this->image_type.'"'); if ($this->image_type == 'text/plain') { $this->image_type = IMAGETYPE_SVG; } } public function reset() { $this->resize_to = 0; } public function checkImage() { logger::log("IMAGEMAGICK", " Imageis type is ".$this->image_type); return $this->image_type; } public function save($filename, $compression) { if ($this->image_type == IMAGETYPE_SVG) { logger::log("IMAGEMAGICK", " Copying SVG file instead of converting"); copy($this->filename, $filename); } else if ($this->convert_path === false) { logger::warn("IMAGEMAGICK", "WARNING! ImageMagick not installed"); copy($this->filename, $filename); } else { if ($this->image_type == IMAGETYPE_PNG) { $params = ' -quality 95'; } else { $params = ' -quality '.$compression.' -alpha remove'; } if ($this->resize_to > 0) { $params .= ' -resize '.$this->resize_to; } $cmd = 'convert "'.$this->filename.'"'.$params.' "'.$filename.'" 2>&1'; logger::trace("IMAGEMAGICK", " Command is ".$cmd); $r = exec($this->convert_path.$cmd, $o, $ret); logger::trace("IMAGEMAGICK", " Final line of output was ".$r); logger::trace("IMAGEMAGICK", " Return Value was ".$ret); // No point trying a copy file fallback, as if ImageMagick can't handle it it's shite. } } public function outputResizedFile() { header('Content-type: '.$this->image_type); readfile($this->filename); } public function resizeToWidth($width) { $this->resize_to = $width; } public function get_image_dimensions() { $width = -1; $height = -1; if ($this->image_type != IMAGETYPE_SVG) { $c = $this->convert_path."identify \"".$this->filename."\" 2>&1"; $o = array(); $r = exec($c, $o); if (preg_match('/ (\d+)x(\d+) /', $r, $matches)) { $width = $matches[1]; $height = $matches[2]; } } return array('width' => $width, 'height' => $height); } public function destroy() { } } class gdImage { private $image; private $resizedimage; private $image_type = false; private $filename; public static function gd_handle_error($errno, $errstr, $errfile, $errline) { logger::warn("GD_IMAGE", "Error",$errno,$errstr,"in",$errfile,"at line",$errline); throw new ErrorException($errstr, 0, $errno, $errfile, $errline); return true; } public function __construct($filename) { set_error_handler('gdImage::gd_handle_error', E_ALL); $this->filename = $filename; logger::log("GD-IMAGE", "Checking File ".$filename); $imgtypes = imagetypes(); try { $image_info = getimagesize($filename); $image_type = $image_info[2]; logger::log("GD-IMAGE", "Image Type is ".$image_type); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } // We're being very careful here to check that the image is of a supported type // without throwing any errors. Belt and braces, since different PHP-GD installations // have different supported types, and IMG_BMP wasn't introduced until PHP7.2 // The case values for the switch statement are always defined to something, since vars.php sets them // to the MIME type of that image if they aren't already defined, as that's what imageMagickImage uses // for its image_type // So if GD is loaded but doesn't support a particular image this sets image_type to false, and imageHandler // falls back to imagemagick. // This list contains all the image types that GD might be able to read, currently. // In the outside chance that an error occurs on a supported image type - sometimes libpng throws a fatal wobbler on some images - // the error handler catches it and we fall back to Imagemagick switch ($image_type) { case IMAGETYPE_JPEG: logger::trace("GD-IMAGE", "Image type is JPEG"); if (defined('IMG_JPG') && ($imgtypes && IMG_JPG) && function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) { try { $this->image = imagecreatefromjpeg($filename); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_GIF: logger::trace("GD-IMAGE", "Image type is GIF"); if (defined('IMG_GIF') && ($imgtypes && IMG_GIF) && function_exists('imagecreatefromgif')) { try { $this->image = imagecreatefromgif($filename); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_PNG: logger::trace("GD-IMAGE", "Image type is PNG"); if (defined('IMG_PNG') && ($imgtypes && IMG_PNG) && function_exists('imagecreatefrompng') && function_exists('imagepng')) { try { $this->image = imagecreatefrompng($filename); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_WBMP: logger::trace("GD-IMAGE", "Image type is WBMP"); if (defined('IMG_WBMP') && ($imgtypes && IMG_WBMP) && function_exists('imagecreatefromwbmp')) { try { $this->image = imagecreatefromwbmp($filename); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_XBM: logger::trace("GD-IMAGE", "Image type is XBM"); if (defined('IMG_XPM') && ($imgtypes && IMG_XPM) && function_exists('imagecreatefromxbm')) { try { $this->image = imagecreatefromxbm($filename); } catch (Exception $e) { logger::warn("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_WEBP: logger::trace("GD-IMAGE", "Image type is WEBP"); if (defined('IMG_WEBP') && ($imgtypes && IMG_WEBP) && function_exists('imagecreatefromwebp')) { try { $this->image = imagecreatefromwebp($filename); } catch (Exception $e) { logger::log("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; case IMAGETYPE_BMP: logger::trace("GD-IMAGE", "Image type is BMP"); if (defined('IMG_BMP') && ($imgtypes && IMG_BMP) && function_exists('imagecreatefrombmp')) { try { $this->image = imagecreatefrombmp($filename); } catch (Exception $e) { logger::log("GD-IMAGE", " GD threw an error when handling",$filename); $image_type = false; } } else { $image_type = false; } break; default: $image_type = false; $this->image_type = false; break; } if ($image_type !== false) { $this->image_type = $image_type; $this->reset(); } } public function checkImage() { if ($this->image_type === false) { logger::warn("GD-IMAGE", " Unsupported Image Type"); } return $this->image_type; } public function reset() { $this->resizedimage = $this->image; imagealphablending($this->resizedimage, false); imagesavealpha($this->resizedimage, true); } public function save($filename, $compression) { if ($this->image_type == IMAGETYPE_PNG) { // Be aware - We always save PNGs as PNGs to preserve alpha channel imagepng($this->resizedimage, $filename, 9); } else { imagejpeg($this->resizedimage, $filename, $compression); } } public function outputResizedFile($size) { if ($this->image_type == IMAGETYPE_PNG) { logger::trace("GD-IMAGE", " Outputting PNG file of size",$size); header('Content-type: image/png'); } else { logger::trace("GD-IMAGE", " Outputting JPEG file of size",$size); header('Content-type: image/jpeg'); } switch ($size) { case 'small': $this->resizeToWidth(100); $this->save(null, 75); break; case 'smallish': $this->resizeToWidth(250); $this->save(null, 70); break; case 'medium': $this->resizeToWidth(400); $this->save(null, 70); break; default: $this->save(null, 90); break; } } public function resizeToWidth($width) { $ratio = $width / $this->getWidth(); $height = $this->getheight() * $ratio; $this->resize($width,$height); } private function getWidth() { return imagesx($this->image); } private function getHeight() { return imagesy($this->image); } private function resize($width, $height) { $this->resizedimage = imagecreatetruecolor($width, $height); imagealphablending($this->resizedimage, false); imagesavealpha($this->resizedimage, true); imagecopyresampled($this->resizedimage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); } public function get_image_dimensions() { return array('width' => $this->getWidth(), 'height' => $this->getHeight()); } public function destroy() { imagedestroy($this->image); } } ?>