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; |
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 |