playlist.php
changeset 40 bd3372a2afc1
parent 33 3b4aef1efff6
child 44 92dd253f501c
equal deleted inserted replaced
39:38dbcda3cf20 40:bd3372a2afc1
    59       'volume.js',
    59       'volume.js',
    60       'dom-drag.js',
    60       'dom-drag.js',
    61       'position.js'
    61       'position.js'
    62     ));
    62     ));
    63   $smarty->assign('allow_control', $allowcontrol);
    63   $smarty->assign('allow_control', $allowcontrol);
       
    64   $smarty->register_function('sprite', 'smarty_function_sprite');
    64   $smarty->display('playlist.tpl');
    65   $smarty->display('playlist.tpl');
    65 }
    66 }
    66 
    67 
    67 function artwork_request_handler($httpd, $socket)
    68 function artwork_request_handler($httpd, $socket)
    68 {
    69 {
    69   global $amarok_home;
    70   global $amarok_home;
       
    71   
       
    72   // get PATH_INFO
       
    73   $pathinfo = @substr(@substr($_SERVER['REQUEST_URI'], 1), @strpos(@substr($_SERVER['REQUEST_URI'], 1), '/')+1);
       
    74   
       
    75   // should we do a collage (for CSS sprites instead of sending hundreds of individual images)?
       
    76   if ( preg_match('/^collage(?:\/([0-9]+))?$/', $pathinfo, $match) )
       
    77   {
       
    78     // default size is 50px per image
       
    79     $collage_size = ( isset($match[1]) ) ? intval($match[1]) : 50;
       
    80     
       
    81     $artwork_dir = "$amarok_home/albumcovers";
       
    82     if ( !file_exists("$artwork_dir/collage_{$collage_size}.png") )
       
    83     {
       
    84       if ( !generate_artwork_collage("$artwork_dir/collage_{$collage_size}.png", $collage_size) )
       
    85       {
       
    86         echo 'Error: generate_artwork_collage() failed';
       
    87         return;
       
    88       }
       
    89     }
       
    90     
       
    91     $target_file = "$artwork_dir/collage_{$collage_size}.png";
       
    92     // we have it now, send the image through
       
    93     $fh = @fopen($target_file, 'r');
       
    94     if ( !$fh )
       
    95       return false;
       
    96     
       
    97     $httpd->header('Content-type: image/png');
       
    98     $httpd->header('Content-length: ' . filesize($target_file));
       
    99     $httpd->header('Expires: Wed, 1 Jan 2020 01:00:00 GMT');
       
   100     
       
   101     // kinda sorta a hack.
       
   102     $headers = implode("\r\n", $httpd->response_headers);
       
   103     $httpd->send_client_headers($socket, $httpd->response_code, $httpd->content_type, $headers);
       
   104     
       
   105     while ( $d = fread($fh, 10240) )
       
   106     {
       
   107       $socket->write($d);
       
   108     }
       
   109     fclose($fh);
       
   110     
       
   111     return;
       
   112   }
    70   
   113   
    71   if ( !isset($_GET['artist']) || !isset($_GET['album']) )
   114   if ( !isset($_GET['artist']) || !isset($_GET['album']) )
    72   {
   115   {
    73     echo 'Please specify artist and album.';
   116     echo 'Please specify artist and album.';
    74     return;
   117     return;
    92     {
   135     {
    93       // not scaled yet, scale to uniform 50x50 image
   136       // not scaled yet, scale to uniform 50x50 image
    94       $artwork_filetype = get_image_filetype("$artwork_dir/large/$artwork_hash");
   137       $artwork_filetype = get_image_filetype("$artwork_dir/large/$artwork_hash");
    95       if ( !$artwork_filetype )
   138       if ( !$artwork_filetype )
    96       {
   139       {
       
   140         // image is not supported (PNG, GIF, or JPG required)
    97         return false;
   141         return false;
    98       }
   142       }
    99       // we'll need to copy the existing artwork file to our thumbnail dir to let scale_image() detect the type properly (it doesn't use magic bytes)
   143       // we'll need to copy the existing artwork file to our thumbnail dir to let scale_image() detect the type properly (it doesn't use magic bytes)
   100       if ( !copy("$artwork_dir/large/$artwork_hash", "$artwork_dir/greyhoundthumbnails/tmp{$artwork_hash}.$artwork_filetype") )
   144       if ( !copy("$artwork_dir/large/$artwork_hash", "$artwork_dir/greyhoundthumbnails/tmp{$artwork_hash}.$artwork_filetype") )
   101       {
   145       {
   115     }
   159     }
   116     // we have it now, send the image through
   160     // we have it now, send the image through
   117     $fh = @fopen($target_file, 'r');
   161     $fh = @fopen($target_file, 'r');
   118     if ( !$fh )
   162     if ( !$fh )
   119       return false;
   163       return false;
       
   164     
   120     $httpd->header('Content-type: image/png');
   165     $httpd->header('Content-type: image/png');
   121     $httpd->header('Content-length: ' . filesize($target_file));
   166     $httpd->header('Content-length: ' . filesize($target_file));
   122     $httpd->header('Expires: Wed, 1 Jan 2020 01:00:00 GMT');
   167     $httpd->header('Expires: Wed, 1 Jan 2020 01:00:00 GMT');
       
   168     
       
   169     // kinda sorta a hack.
       
   170     $headers = implode("\r\n", $httpd->response_headers);
       
   171     $httpd->send_client_headers($socket, $httpd->response_code, $httpd->content_type, $headers);
       
   172     
   123     while ( !feof($fh) )
   173     while ( !feof($fh) )
   124     {
   174     {
   125       socket_write($socket, fread($fh, 51200));
   175       $socket->write(fread($fh, 51200));
   126     }
   176     }
   127     fclose($fh);
   177     fclose($fh);
   128   }
   178   }
   129   else
   179   else
   130   {
   180   {
   133     $al = htmlspecialchars($_GET['album']);
   183     $al = htmlspecialchars($_GET['album']);
   134     $httpd->send_http_error($socket, 404, "The requested artwork file for $ar:$al could not be found on this server.");
   184     $httpd->send_http_error($socket, 404, "The requested artwork file for $ar:$al could not be found on this server.");
   135   }
   185   }
   136 }
   186 }
   137 
   187 
   138 
   188 /**
       
   189  * Generates a collage of all album art for use as a CSS sprite. Also generates a textual .map file in the format of "hash xpos ypos\n"
       
   190  * to allow retrieving positions of images. Requires GD.
       
   191  * @param string Name of the collage file. Map file will be the same filename except with the extension ".map"
       
   192  * @param int Size of each image, in pixels. Artwork images will be stretched to a 1:1 aspect ratio. Optional, defaults to 50.
       
   193  * @return bool True on success, false on failure.
       
   194  */
       
   195 
       
   196 function generate_artwork_collage($target_file, $size = 50)
       
   197 {
       
   198   // check for required GD functionality
       
   199   if ( !function_exists('imagecopyresampled') || !function_exists('imagepng') )
       
   200     return false;
       
   201   
       
   202   status("generating size $size collage");
       
   203   $stderr = fopen('php://stderr', 'w');
       
   204   if ( !$stderr )
       
   205     // this should really never fail.
       
   206     return false;
       
   207   
       
   208   // import amarok globals
       
   209   global $amarok_home;
       
   210   $artwork_dir = "$amarok_home/albumcovers";
       
   211   
       
   212   // map file path
       
   213   $mapfile = preg_replace('/\.[a-z]+$/', '', $target_file) . '.map';
       
   214   
       
   215   // open map file
       
   216   $maphandle = @fopen($mapfile, 'w');
       
   217   if ( !$maphandle )
       
   218     return false;
       
   219   
       
   220   $mapheader = <<<EOF
       
   221 # this artwork collage map gives the locations of various artwork images within the collage
       
   222 # format is:
       
   223 # hash                           x y
       
   224 # x and y are indices, not pixel values (obviously), and hash is the name of the artwork file in large/
       
   225 
       
   226 EOF;
       
   227   fwrite($maphandle, $mapheader);
       
   228   
       
   229   // build a list of existing artwork files
       
   230   $artwork_list = array();
       
   231   if ( $dh = @opendir("$artwork_dir/large") )
       
   232   {
       
   233     while ( $fp = @readdir($dh) )
       
   234     {
       
   235       if ( preg_match('/^[a-f0-9]{32}$/', $fp) )
       
   236       {
       
   237         $artwork_list[] = $fp;
       
   238       }
       
   239     }
       
   240     closedir($dh);
       
   241   }
       
   242   else
       
   243   {
       
   244     return false;
       
   245   }
       
   246   
       
   247   // at least one image?
       
   248   if ( empty($artwork_list) )
       
   249     return false;
       
   250   
       
   251   // asort it to make sure map is predictable
       
   252   asort($artwork_list);
       
   253   
       
   254   // number of columns
       
   255   $cols = 20;
       
   256   // number of rows
       
   257   $rows = ceil( count($artwork_list) / $cols );
       
   258   
       
   259   // image dimensions
       
   260   $image_width  = $cols * $size;
       
   261   $image_height = $rows * $size;
       
   262   
       
   263   // create image
       
   264   $collage = imagecreatetruecolor($image_width, $image_height);
       
   265   
       
   266   // generator loop
       
   267   // start at row 0, column 0
       
   268   $col = -1;
       
   269   $row = 0;
       
   270   $srow = $row + 1;
       
   271   fwrite($stderr, "  -> row $srow of $rows\r");
       
   272   $time_map = microtime(true);
       
   273   foreach ( $artwork_list as $artwork_file )
       
   274   {
       
   275     // calculate where we are
       
   276     $col++;
       
   277     if ( $col == $cols )
       
   278     {
       
   279       // reached column limit, reset $cols and increment row
       
   280       $col = 0;
       
   281       $row++;
       
   282       $srow = $row + 1;
       
   283       fwrite($stderr, "  -> row $srow of $rows\r");
       
   284     }
       
   285     // x and y offset of scaled image
       
   286     $xoff = $col * $size;
       
   287     $yoff = $row * $size;
       
   288     // set offset
       
   289     fwrite($maphandle, "$artwork_file $col $row\n");
       
   290     // load image
       
   291     $createfunc = ( get_image_filetype("$artwork_dir/large/$artwork_file") == 'jpg' ) ? 'imagecreatefromjpeg' : 'imagecreatefrompng';
       
   292     $aw = @$createfunc("$artwork_dir/large/$artwork_file");
       
   293     if ( !$aw )
       
   294     {
       
   295       $aw = @imagecreatefromwbmp("$artwork_dir/large/$artwork_file");
       
   296       if ( !$aw )
       
   297       {
       
   298         // couldn't load image, silently continue
       
   299         continue;
       
   300       }
       
   301     }
       
   302     list($aw_width, $aw_height) = array(imagesx($aw), imagesy($aw));
       
   303     // scale and position image
       
   304     $result = imagecopyresampled($collage, $aw, $xoff, $yoff, 0, 0, $size, $size, $aw_width, $aw_height);
       
   305     if ( !$result )
       
   306     {
       
   307       // couldn't scale image, silently continue
       
   308       continue;
       
   309     }
       
   310     // free the temp image
       
   311     imagedestroy($aw);
       
   312   }
       
   313   $time_map = round(1000 * (microtime(true) - $time_map));
       
   314   $time_write = microtime(true);
       
   315   fclose($maphandle);
       
   316   fwrite($stderr, "  -> saving image\r");
       
   317   if ( !imagepng($collage, $target_file) )
       
   318     return false;
       
   319   imagedestroy($collage);
       
   320   $time_write = round(1000 * (microtime(true) - $time_write));
       
   321   
       
   322   $avg = round($time_map / count($artwork_list));
       
   323   
       
   324   status("collage generation complete, returning success; time (ms): map/avg/write $time_map/$avg/$time_write");
       
   325   return true;
       
   326 }
       
   327 
       
   328 /**
       
   329  * Returns an img tag showing artwork from the specified size collage sprite.
       
   330  * @param string Artist
       
   331  * @param string Album
       
   332  * @param int Collage size
       
   333  * @return string
       
   334  */
       
   335 
       
   336 function get_artwork_sprite($artist, $album, $size = 50)
       
   337 {
       
   338   // import amarok globals
       
   339   global $amarok_home;
       
   340   $artwork_dir = "$amarok_home/albumcovers";
       
   341   
       
   342   if ( !is_int($size) )
       
   343     return '';
       
   344   
       
   345   // hash of cover
       
   346   $coverid = md5(strtolower(trim($artist)) . strtolower(trim($album)));
       
   347   
       
   348   $tag = '<img alt=" " src="/spacer.gif" width="' . $size . '" height="' . $size . '" ';
       
   349   if ( file_exists("$artwork_dir/collage_{$size}.map") )
       
   350   {
       
   351     $mapdata = parse_collage_map("$artwork_dir/collage_{$size}.map");
       
   352     if ( isset($mapdata[$coverid]) )
       
   353     {
       
   354       $css_x = -1 * $size * $mapdata[$coverid][0];
       
   355       $css_y = -1 * $size * $mapdata[$coverid][1];
       
   356       $tag .= "style=\"background-image: url(/artwork/collage/$size); background-repeat: no-repeat; background-position: {$css_x}px {$css_y}px;\" ";
       
   357     }
       
   358   }
       
   359   $tag .= '/>';
       
   360   
       
   361   return $tag;
       
   362 }
       
   363 
       
   364 /**
       
   365  * Parses the specified artwork map file. Return an associative array, keys being the artwork file hashes and values being array(x, y).
       
   366  * @param string Map file
       
   367  * @return array
       
   368  */
       
   369 
       
   370 function parse_collage_map($mapfile)
       
   371 {
       
   372   if ( !file_exists($mapfile) )
       
   373     return array();
       
   374   
       
   375   $fp = @fopen($mapfile, 'r');
       
   376   if ( !$fp )
       
   377     return false;
       
   378   
       
   379   $map = array();
       
   380   while ( $line = fgets($fp) )
       
   381   {
       
   382     // parse out comments
       
   383     $line = trim(preg_replace('/#(.+)$/', '', $line));
       
   384     if ( empty($line) )
       
   385       continue;
       
   386     list($hash, $x, $y) = explode(' ', $line);
       
   387     if ( !preg_match('/^[a-f0-9]{32}$/', $hash) || !preg_match('/^[0-9]+$/', $x) || !preg_match('/^[0-9]+$/', $y) )
       
   388       // invalid line
       
   389       continue;
       
   390       
       
   391     // valid line, append map array
       
   392     $map[$hash] = array(
       
   393         intval($x),
       
   394         intval($y)
       
   395       );
       
   396   }
       
   397   fclose($fp);
       
   398   return $map;
       
   399 }
       
   400 
       
   401 /**
       
   402  * Finds out if a collage file is outdated (e.g. missing artwork images)
       
   403  * @param int Size of collage
       
   404  * @return bool true if outdated
       
   405  */
       
   406 
       
   407 function collage_is_outdated($size = 50)
       
   408 {
       
   409   global $amarok_home;
       
   410   $artwork_dir = "$amarok_home/albumcovers";
       
   411   $mapfile = "$artwork_dir/collage_{$size}.map";
       
   412   if ( !file_exists($mapfile) )
       
   413   {
       
   414     // consider it outdated if it doesn't exist
       
   415     return true;
       
   416   }
       
   417   
       
   418   // load existing image map
       
   419   $map = parse_collage_map($mapfile);
       
   420   
       
   421   // build a list of existing artwork files
       
   422   $artwork_list = array();
       
   423   if ( $dh = @opendir("$artwork_dir/large") )
       
   424   {
       
   425     while ( $fp = @readdir($dh) )
       
   426     {
       
   427       if ( preg_match('/^[a-f0-9]{32}$/', $fp) )
       
   428       {
       
   429         // found an artwork file
       
   430         if ( !isset($map[$fp]) )
       
   431         {
       
   432           // this artwork isn't in the map file, return outdated
       
   433           closedir($dh);
       
   434           status("size $size collage is outdated");
       
   435           return true;
       
   436         }
       
   437       }
       
   438     }
       
   439     closedir($dh);
       
   440   }
       
   441   
       
   442   // if we reach here, we haven't found anything missing.
       
   443   return false;
       
   444 }
       
   445 
       
   446 /**
       
   447  * Smarty function for sprite generation.
       
   448  * @access private
       
   449  */
       
   450 
       
   451 function smarty_function_sprite($params, &$smarty)
       
   452 {
       
   453   // don't perform the exhaustive check more than once per execution
       
   454   static $checks_done = array();
       
   455   
       
   456   if ( empty($params['artist']) )
       
   457     return 'Error: missing "artist" parameter';
       
   458   if ( empty($params['album']) )
       
   459     return 'Error: missing "album" parameter';
       
   460   if ( empty($params['size']) )
       
   461     $params['size'] = 50;
       
   462   
       
   463   $params['size'] = intval($params['size']);
       
   464   $size =& $params['size'];
       
   465   
       
   466   // if the collage file doesn't exist or is missing artwork, renew it
       
   467   // but only perform this check once per execution per size
       
   468   if ( !isset($checks_done[$size]) )
       
   469   {
       
   470     global $amarok_home;
       
   471     $artwork_dir = "$amarok_home/albumcovers";
       
   472     
       
   473     $collage_file = "$artwork_dir/collage_{$size}";
       
   474     $collage_is_good = file_exists("$collage_file.png") && file_exists("$collage_file.map") && !collage_is_outdated($size);
       
   475     if ( !$collage_is_good )
       
   476     {
       
   477       generate_artwork_collage("$collage_file.png", $size);
       
   478     }
       
   479     $checks_done[$size] = true;
       
   480   }
       
   481   
       
   482   return get_artwork_sprite($params['artist'], $params['album'], $params['size']);
       
   483 }
       
   484