Back ported last.fm coverart service from Subsonic 6.0_beta1

None of this code is mine, I just integrated it.
master
Zak Peirce 8 years ago
parent 2b236e8d47
commit 72851f0721
  1. 10
      libresonic-main/pom.xml
  2. 14
      libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java
  3. 5
      libresonic-main/src/main/java/org/libresonic/player/controller/ChangeCoverArtController.java
  4. 69
      libresonic-main/src/main/java/org/libresonic/player/domain/AlbumNotes.java
  5. 50
      libresonic-main/src/main/java/org/libresonic/player/domain/LastFmCoverArt.java
  6. 49
      libresonic-main/src/main/java/org/libresonic/player/service/LastFmExpirationPolicy.java
  7. 101
      libresonic-main/src/main/java/org/libresonic/player/service/LastFmService.java
  8. 3
      libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties
  9. 1
      libresonic-main/src/main/webapp/WEB-INF/applicationContext-service.xml
  10. 4
      libresonic-main/src/main/webapp/WEB-INF/dwr.xml
  11. 146
      libresonic-main/src/main/webapp/WEB-INF/jsp/changeCoverArt.jsp
  12. 287
      libresonic-main/src/main/webapp/WEB-INF/jsp/coverArt.jsp
  13. BIN
      libresonic-main/src/main/webapp/icons/lastfm.gif

@ -123,14 +123,8 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-base</artifactId>
<version>r03</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-collections</artifactId>
<version>r03</version>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>

@ -23,6 +23,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.config.RequestConfig;
@ -32,7 +33,9 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.LastFmCoverArt;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
@ -50,6 +53,11 @@ public class CoverArtService {
private SecurityService securityService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
public List<LastFmCoverArt> searchCoverArt(String artist, String album) {
return lastFmService.searchCoverArt(artist, album);
}
/**
* Downloads and saves the cover art at the given URL.
@ -162,4 +170,8 @@ public class CoverArtService {
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
}

@ -25,6 +25,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
@ -49,10 +50,10 @@ public class ChangeCoverArtController extends ParameterizableViewController {
String album = request.getParameter("album");
MediaFile dir = mediaFileService.getMediaFile(id);
if (artist == null) {
if (StringUtils.isBlank(artist)) {
artist = dir.getArtist();
}
if (album == null) {
if (StringUtils.isBlank(album)) {
album = dir.getAlbumName();
}

@ -0,0 +1,69 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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 3 of the License, or
* (at your option) any later version.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2016 (C) Libresonic Authors
* Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.domain;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class AlbumNotes {
private final String notes;
private final String musicBrainzId;
private final String lastFmUrl;
private final String smallImageUrl;
private final String mediumImageUrl;
private final String largeImageUrl;
public AlbumNotes(String notes, String musicBrainzId, String lastFmUrl, String smallImageUrl,
String mediumImageUrl, String largeImageUrl) {
this.notes = notes;
this.musicBrainzId = musicBrainzId;
this.lastFmUrl = lastFmUrl;
this.smallImageUrl = smallImageUrl;
this.mediumImageUrl = mediumImageUrl;
this.largeImageUrl = largeImageUrl;
}
public String getNotes() {
return notes;
}
public String getMusicBrainzId() {
return musicBrainzId;
}
public String getLastFmUrl() {
return lastFmUrl;
}
public String getSmallImageUrl() {
return smallImageUrl;
}
public String getMediumImageUrl() {
return mediumImageUrl;
}
public String getLargeImageUrl() {
return largeImageUrl;
}
}

@ -0,0 +1,50 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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 3 of the License, or
* (at your option) any later version.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package org.libresonic.player.domain;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class LastFmCoverArt {
private final String imageUrl;
private final String artist;
private final String album;
public LastFmCoverArt(String imageUrl, String artist, String album) {
this.imageUrl = imageUrl;
this.artist = artist;
this.album = album;
}
public String getImageUrl() {
return imageUrl;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
}

@ -0,0 +1,49 @@
/**
* This file is part of Libresonic.
*
* Libresonic 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 3 of the License, or
* (at your option) any later version.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package org.libresonic.player.service;
import java.util.LinkedHashMap;
import java.util.Map;
import de.umass.lastfm.cache.ExpirationPolicy;
/**
* Artist and album info is cached permanently. Everything else is cached one year.
*
* @author Sindre Mehus
* @version $Id$
*/
public class LastFmExpirationPolicy implements ExpirationPolicy {
private final static long ONE_YEAR = 12 * 30 * 24 * 3600 * 1000L;
private final Map<String, Long> methodToExpirationTime = new LinkedHashMap<String, Long>() {{
put("artist.getInfo", Long.MAX_VALUE); // Cache forever
put("album.getInfo", Long.MAX_VALUE); // Cache forever
put("album.search", -1L); // Don't cache
}};
@Override
public long getExpirationTime(String method, Map<String, String> params) {
Long expirationTime = methodToExpirationTime.get(method);
return expirationTime == null ? ONE_YEAR : expirationTime;
}
}

@ -22,6 +22,7 @@ package org.libresonic.player.service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -30,13 +31,23 @@ import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import de.umass.lastfm.Album;
import de.umass.lastfm.Artist;
import de.umass.lastfm.Caller;
import de.umass.lastfm.ImageSize;
import de.umass.lastfm.Track;
import de.umass.lastfm.cache.Cache;
import org.libresonic.player.Logger;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.LastFmCoverArt;
import org.libresonic.player.domain.AlbumNotes;
import org.libresonic.player.domain.ArtistBio;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
@ -273,6 +284,96 @@ public class LastFmService {
}
}
/**
* Returns album notes and images.
*
* @param mediaFile The media file (song or album).
* @return Album notes.
*/
public AlbumNotes getAlbumNotes(MediaFile mediaFile) {
return getAlbumNotes(getCanonicalArtistName(getArtistName(mediaFile)), mediaFile.getAlbumName());
}
/**
* Returns album notes and images.
*
* @param album The album.
* @return Album notes.
*/
public AlbumNotes getAlbumNotes(org.libresonic.player.domain.Album album) {
return getAlbumNotes(getCanonicalArtistName(album.getArtist()), album.getName());
}
/**
* Returns album notes and images.
*
* @param artist The artist name.
* @param album The album name.
* @return Album notes.
*/
private AlbumNotes getAlbumNotes(String artist, String album) {
if (artist == null || album == null) {
return null;
}
try {
Album info = Album.getInfo(artist, album, LAST_FM_KEY);
if (info == null) {
return null;
}
return new AlbumNotes(processWikiText(info.getWikiSummary()),
info.getMbid(),
info.getUrl(),
info.getImageURL(ImageSize.MEDIUM),
info.getImageURL(ImageSize.LARGE),
info.getImageURL(ImageSize.MEGA));
} catch (Throwable x) {
LOG.warn("Failed to find album notes for " + artist + " - " + album, x);
return null;
}
}
public List<LastFmCoverArt> searchCoverArt(String artist, String album) {
if (artist == null && album == null) {
return Collections.emptyList();
}
try {
StringBuilder query = new StringBuilder();
if (artist != null) {
query.append(artist).append(" ");
}
if (album != null) {
query.append(album);
}
Collection<Album> matches = Album.search(query.toString(), LAST_FM_KEY);
return FluentIterable.from(matches)
.transform(new Function<Album, LastFmCoverArt>() {
@Override
public LastFmCoverArt apply(Album album) {
return convert(album);
}
})
.filter(Predicates.notNull())
.toList();
} catch (Throwable x) {
LOG.warn("Failed to search for cover art for " + artist + " - " + album, x);
return Collections.emptyList();
}
}
private LastFmCoverArt convert(Album album) {
String imageUrl = null;
for (ImageSize imageSize : Lists.reverse(Arrays.asList(ImageSize.values()))) {
imageUrl = StringUtils.trimToNull(album.getImageURL(imageSize));
if (imageUrl != null) {
break;
}
}
return imageUrl == null ? null : new LastFmCoverArt(imageUrl, album.getArtist(), album.getName());
}
private ArtistBio getArtistBio(String artistName) {
try {
if (artistName == null) {

@ -666,11 +666,12 @@ changecoverart.title = Change cover art
changecoverart.address = Or enter image address
changecoverart.artist = Artist
changecoverart.album = Album
changecoverart.search = Google Image Search
changecoverart.search = Search Last.fm
changecoverart.wait = Please wait...
changecoverart.success = Image was successfully downloaded.
changecoverart.error = Failed to download image.
changecoverart.noimagesfound = No images found.
changecoverart.courtesy = Images courtesy of Last.fm
# changeCoverArtConfirm.jsp
changeCoverArtConfirm.failed = Failed to change cover art:<br><b>"{0}"</b>

@ -274,6 +274,7 @@
<bean id="ajaxCoverArtService" class="org.libresonic.player.ajax.CoverArtService">
<property name="securityService" ref="securityService"/>
<property name="mediaFileService" ref="mediaFileService"/>
<property name="lastFmService" ref="lastFmService"/>
</bean>
<bean id="ajaxStarService" class="org.libresonic.player.ajax.StarService">

@ -53,6 +53,8 @@
<convert converter="bean" match="org.libresonic.player.ajax.PlaylistInfo$Entry"/>
<convert converter="bean" match="org.libresonic.player.domain.Playlist"/>
<convert converter="bean" match="org.libresonic.player.domain.ArtistBio"/>
<convert converter="bean" match="org.libresonic.player.domain.LastFmCoverArt"/>
<convert converter="bean" match="org.libresonic.player.ajax.UploadInfo"/>
<convert converter="bean" match="org.libresonic.player.ajax.LyricsInfo"/>
<convert converter="bean" match="org.libresonic.player.ajax.CoverArtInfo"/>
@ -63,4 +65,4 @@
<convert converter="bean" match="org.libresonic.player.ajax.TopSong"/>
</allow>
</dwr>
</dwr>

@ -6,13 +6,14 @@
<script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
<script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
<script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" language="javascript">
dwr.engine.setErrorHandler(null);
google.load('search', '1');
var imageSearch;
dwr.engine.setErrorHandler(function() {
$("wait").hide();
dwr.util.setValue("errorDetails", "Sorry, an error occurred while searching for cover art.");
$("errorDetails").show();
});
function setImage(imageUrl) {
$("wait").show();
@ -21,14 +22,13 @@
$("error").hide();
$("errorDetails").hide();
$("noImagesFound").hide();
var id = dwr.util.getValue("id");
coverArtService.setCoverArtImage(id, imageUrl, setImageComplete);
coverArtService.setCoverArtImage(${model.id}, imageUrl, setImageComplete);
}
function setImageComplete(errorDetails) {
$("wait").hide();
if (errorDetails != null) {
dwr.util.setValue("errorDetails", "<br/>" + errorDetails, { escapeHtml:false });
dwr.util.setValue("errorDetails", errorDetails, { escapeHtml:false });
$("error").show();
$("errorDetails").show();
} else {
@ -36,37 +36,29 @@
}
}
function searchComplete() {
function searchComplete(searchResults) {
$("wait").hide();
if (imageSearch.results && imageSearch.results.length > 0) {
if (searchResults.length > 0) {
var images = $("images");
images.innerHTML = "";
var results = imageSearch.results;
for (var i = 0; i < results.length; i++) {
var result = results[i];
for (var i = 0; i < searchResults.length; i++) {
var result = searchResults[i];
var node = $("template").cloneNode(true);
// Rename results to https to avoid mixed contents.
result.tbUrl = result.tbUrl.replace('http://', 'https://');
var link = node.getElementsByClassName("search-result-link")[0];
link.href = "javascript:setImage('" + result.url + "');";
var thumbnail = node.getElementsByClassName("search-result-thumbnail")[0];
thumbnail.src = result.tbUrl;
link.href = "javascript:setImage('" + result.imageUrl + "');";
var title = node.getElementsByClassName("search-result-title")[0];
title.innerHTML = result.contentNoFormatting.truncate(30);
var thumbnail = node.getElementsByClassName("search-result-image")[0];
thumbnail.src = result.imageUrl;
var dimension = node.getElementsByClassName("search-result-dimension")[0];
dimension.innerHTML = result.width + " × " + result.height;
var title = node.getElementsByClassName("search-result-artist")[0];
title.innerHTML = result.artist;
var url = node.getElementsByClassName("search-result-url")[0];
url.innerHTML = result.visibleUrl;
var dimension = node.getElementsByClassName("search-result-album")[0];
dimension.innerHTML = result.album;
node.show();
images.appendChild(node);
@ -74,50 +66,12 @@
$("result").show();
addPaginationLinks(imageSearch);
} else {
$("noImagesFound").show();
}
}
function addPaginationLinks() {
// To paginate search results, use the cursor function.
var cursor = imageSearch.cursor;
var curPage = cursor.currentPageIndex; // check what page the app is on
var pagesDiv = document.createElement("div");
for (var i = 0; i < cursor.pages.length; i++) {
var page = cursor.pages[i];
var label;
if (curPage == i) {
// If we are on the current page, then don"t make a link.
label = document.createElement("b");
} else {
// Create links to other pages using gotoPage() on the searcher.
label = document.createElement("a");
label.href = "javascript:imageSearch.gotoPage(" + i + ");";
}
label.innerHTML = page.label;
label.style.marginRight = "1em";
pagesDiv.appendChild(label);
}
// Create link to next page.
if (curPage < cursor.pages.length - 1) {
var next = document.createElement("a");
next.href = "javascript:imageSearch.gotoPage(" + (curPage + 1) + ");";
next.innerHTML = "<fmt:message key="common.next"/>";
next.style.marginLeft = "1em";
pagesDiv.appendChild(next);
}
var pages = $("pages");
pages.innerHTML = "";
pages.appendChild(pagesDiv);
}
function search() {
$("wait").show();
@ -127,40 +81,25 @@
$("errorDetails").hide();
$("noImagesFound").hide();
var query = dwr.util.getValue("query");
imageSearch.execute(query);
}
function onLoad() {
imageSearch = new google.search.ImageSearch();
imageSearch.setSearchCompleteCallback(this, searchComplete, null);
imageSearch.setNoHtmlGeneration();
imageSearch.setResultSetSize(8);
google.search.Search.getBranding("branding");
$("template").hide();
search();
var artist = dwr.util.getValue("artist");
var album = dwr.util.getValue("album");
coverArtService.searchCoverArt(artist, album, searchComplete);
}
google.setOnLoadCallback(onLoad);
</script>
</head>
<body class="mainframe bgcolor1">
<body class="mainframe bgcolor1" onload="search()">
<h1><fmt:message key="changecoverart.title"/></h1>
<form action="javascript:search()">
<table class="indent"><tr>
<td><input id="query" name="query" size="70" type="text" value="${model.artist} ${model.album}" onclick="select()"/></td>
<td><input id="artist" name="artist" placeholder="<fmt:message key="changecoverart.artist"/>" size="35" type="text" value="${model.artist}" onclick="select()"/></td>
<td><input id="album" name="album" placeholder="<fmt:message key="changecoverart.album"/>" size="35" type="text" value="${model.album}" onclick="select()"/></td>
<td style="padding-left:0.5em"><input type="submit" value="<fmt:message key="changecoverart.search"/>"/></td>
</tr></table>
</form>
<form action="javascript:setImage(dwr.util.getValue('url'))">
<table><tr>
<input id="id" type="hidden" name="id" value="${model.id}"/>
<td><label for="url"><fmt:message key="changecoverart.address"/></label></td>
<td style="padding-left:0.5em"><input type="text" name="url" size="50" id="url" value="http://" onclick="select()"/></td>
<td style="padding-left:0.5em"><input type="submit" value="<fmt:message key="common.ok"/>"></td>
@ -178,32 +117,21 @@
<div id="errorDetails" class="warning" style="display:none">
</div>
<div id="result">
<div id="pages" style="float:left;padding-left:0.5em;padding-top:0.5em">
</div>
<div id="branding" style="float:right;padding-right:1em;padding-top:0.5em">
</div>
<div style="clear:both;">
</div>
<div id="images" style="width:100%;padding-bottom:2em">
</div>
<div style="clear:both;">
</div>
<div id="result" style="padding-top:2em">
<div style="clear:both;"></div>
<div id="images"></div>
<div style="clear:both;"></div>
<a href="http://last.fm/" target="_blank"><img src="<c:url value="/icons/lastfm.gif"/>"></a>
<span class="detail" style="padding-left:1em"><fmt:message key="changecoverart.courtesy"/></span>
</div>
<div id="template" style="float:left; height:190px; width:220px;padding:0.5em;position:relative">
<div style="position:absolute;bottom:0">
<a class="search-result-link"><img class="search-result-thumbnail" style="padding:1px; border:1px solid #021a40; background-color:white;"></a>
<div class="search-result-title"></div>
<div class="search-result-dimension detail"></div>
<div class="search-result-url detail"></div>
<div id="template" class="coverart dropshadow" style="float:left;margin-right:2.0em;margin-bottom:2.0em;width:250px;display:none">
<div>
<a class="search-result-link"><img class="search-result-image" style="width:250px;height:250px"></a>
<div class="search-result-artist caption1"></div>
<div class="search-result-album caption2"></div>
</div>
</div>
</body></html>
</body></html>

@ -1,144 +1,143 @@
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
<%@ include file="include.jsp" %>
<%--
PARAMETERS
albumId: ID of album.
playlistId: ID of playlist.
podcastChannelId: ID of podcast channel
coverArtSize: Height and width of cover art.
caption1: Caption line 1
caption2: Caption line 2
caption3: Caption line 3
captionCount: Number of caption lines to display (default 0)
showLink: Whether to make the cover art image link to the album page.
showZoom: Whether to display a link for zooming the cover art.
showChange: Whether to display a link for changing the cover art.
appearAfter: Fade in after this many milliseconds, or nil if no fading in should happen.
hideOverflow: Hide cover art overflow when height is greater than width
--%>
<c:choose>
<c:when test="${empty param.coverArtSize}">
<c:set var="size" value="auto"/>
</c:when>
<c:otherwise>
<c:set var="size" value="${param.coverArtSize}px"/>
</c:otherwise>
</c:choose>
<c:set var="captionCount" value="${empty param.captionCount ? 0 : param.captionCount}"/>
<str:randomString count="5" type="alphabet" var="divId"/>
<str:randomString count="5" type="alphabet" var="imgId"/>
<str:randomString count="5" type="alphabet" var="playId"/>
<div class="coverart dropshadow">
<div style="width:${size};max-width:${size};height:${size};max-height:${size};cursor:pointer;<c:if test="${param.hideOverflow}">overflow:hidden</c:if>;" title="${param.caption1}" id="${divId}">
<c:if test="${not empty param.albumId}">
<c:url value="main.view" var="targetUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
</c:if>
<c:if test="${not empty param.playlistId}">
<c:url value="playlist.view" var="targetUrl">
<c:param name="id" value="${param.playlistId}"/>
</c:url>
</c:if>
<c:if test="${not empty param.podcastChannelId}">
<c:url value="podcastChannel.view" var="targetUrl">
<c:param name="id" value="${param.podcastChannelId}"/>
</c:url>
</c:if>
<c:url value="/coverArt.view" var="coverArtUrl">
<c:if test="${not empty param.coverArtSize}">
<c:param name="size" value="${param.coverArtSize}"/>
</c:if>
<c:if test="${not empty param.albumId}">
<c:param name="id" value="${param.albumId}"/>
</c:if>
<c:if test="${not empty param.podcastChannelId}">
<c:param name="id" value="pod-${param.podcastChannelId}"/>
</c:if>
<c:if test="${not empty param.playlistId}">
<c:param name="id" value="pl-${param.playlistId}"/>
</c:if>
</c:url>
<c:url value="/coverArt.view" var="zoomCoverArtUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
<div style="position: relative; width: 0; height: 0">
<img src="<spring:theme code="playOverlayImage"/>" id="${playId}"
style="position: relative; top: 8px; left: 8px; z-index: 2; display:none" >
</div>
<c:choose>
<c:when test="${param.showLink}"><a href="${targetUrl}" title="${param.caption1}"></c:when>
<c:when test="${param.showZoom}"><a href="${zoomCoverArtUrl}" rel="zoom" title="${param.caption1}"></c:when>
</c:choose>
<img src="${coverArtUrl}" id="${imgId}" alt="${param.caption1}"
style="display:none">
<c:if test="${param.showLink or param.showZoom}"></a></c:if>
</div>
<c:if test="${captionCount gt 0}">
<div class="caption1" style="width:${param.coverArtSize - 16}px"><a href="${targetUrl}" title="${param.caption1}">${param.caption1}</a></div>
</c:if>
<c:if test="${captionCount gt 1}">
<div class="caption2" style="width:${param.coverArtSize - 16}px">${param.caption2}&nbsp;</div>
</c:if>
<c:if test="${captionCount gt 2}">
<div class="caption3" style="width:${param.coverArtSize - 16}px">${param.caption3}&nbsp;</div>
</c:if>
</div>
<c:if test="${param.showChange or param.showZoom}">
<div style="padding-top:6px;text-align:right">
<c:if test="${param.showChange}">
<c:url value="/changeCoverArt.view" var="changeCoverArtUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
<a class="detail" href="${changeCoverArtUrl}"><fmt:message key="coverart.change"/></a>
</c:if>
<c:if test="${param.showZoom and param.showChange}">
|
</c:if>
<c:if test="${param.showZoom}">
<a class="detail" rel="zoom" title="${param.caption1}" href="${zoomCoverArtUrl}"><fmt:message key="coverart.zoom"/></a>
</c:if>
</div>
</c:if>
<script type="text/javascript">
$(document).ready(function () {
setTimeout("$('#${imgId}').fadeIn(500)", ${empty param.appearAfter ? 0 : param.appearAfter});
});
$("#${divId}").mouseenter(function () {
$("#${playId}").show();
$("#${imgId}").stop();
$("#${imgId}").animate({opacity: 0.7}, 150);
});
$("#${divId}").mouseleave(function () {
$("#${playId}").hide();
$("#${imgId}").stop();
$("#${imgId}").animate({opacity: 1.0}, 150);
});
$("#${playId}").click(function () {
<c:if test="${not empty param.albumId}">
top.playQueue.onPlay(${param.albumId});
</c:if>
<c:if test="${not empty param.playlistId}">
top.playQueue.onPlayPlaylist(${param.playlistId});
</c:if>
<c:if test="${not empty param.podcastChannelId}">
top.playQueue.onPlayPodcastChannel(${param.podcastChannelId});
</c:if>
});
</script>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
<%@ include file="include.jsp" %>
<%--
PARAMETERS
albumId: ID of album.
playlistId: ID of playlist.
podcastChannelId: ID of podcast channel
coverArtSize: Height and width of cover art.
caption1: Caption line 1
caption2: Caption line 2
caption3: Caption line 3
captionCount: Number of caption lines to display (default 0)
showLink: Whether to make the cover art image link to the album page.
showZoom: Whether to display a link for zooming the cover art.
showChange: Whether to display a link for changing the cover art.
appearAfter: Fade in after this many milliseconds, or nil if no fading in should happen.
--%>
<c:choose>
<c:when test="${empty param.coverArtSize}">
<c:set var="size" value="auto"/>
</c:when>
<c:otherwise>
<c:set var="size" value="${param.coverArtSize}px"/>
</c:otherwise>
</c:choose>
<c:set var="captionCount" value="${empty param.captionCount ? 0 : param.captionCount}"/>
<str:randomString count="5" type="alphabet" var="divId"/>
<str:randomString count="5" type="alphabet" var="imgId"/>
<str:randomString count="5" type="alphabet" var="playId"/>
<div class="coverart dropshadow">
<div style="width:${size};max-width:${size};height:${size};max-height:${size};cursor:pointer" title="${param.caption1}" id="${divId}">
<c:if test="${not empty param.albumId}">
<c:url value="main.view" var="targetUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
</c:if>
<c:if test="${not empty param.playlistId}">
<c:url value="playlist.view" var="targetUrl">
<c:param name="id" value="${param.playlistId}"/>
</c:url>
</c:if>
<c:if test="${not empty param.podcastChannelId}">
<c:url value="podcastChannel.view" var="targetUrl">
<c:param name="id" value="${param.podcastChannelId}"/>
</c:url>
</c:if>
<c:url value="/coverArt.view" var="coverArtUrl">
<c:if test="${not empty param.coverArtSize}">
<c:param name="size" value="${param.coverArtSize}"/>
</c:if>
<c:if test="${not empty param.albumId}">
<c:param name="id" value="${param.albumId}"/>
</c:if>
<c:if test="${not empty param.podcastChannelId}">
<c:param name="id" value="pod-${param.podcastChannelId}"/>
</c:if>
<c:if test="${not empty param.playlistId}">
<c:param name="id" value="pl-${param.playlistId}"/>
</c:if>
</c:url>
<c:url value="/coverArt.view" var="zoomCoverArtUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
<div style="position: relative; width: 0; height: 0">
<img src="<spring:theme code="playOverlayImage"/>" id="${playId}"
style="position: relative; top: 8px; left: 8px; z-index: 2; display:none" >
</div>
<c:choose>
<c:when test="${param.showLink}"><a href="${targetUrl}" title="${param.caption1}"></c:when>
<c:when test="${param.showZoom}"><a href="${zoomCoverArtUrl}" rel="zoom" title="${param.caption1}"></c:when>
</c:choose>
<img src="${coverArtUrl}" id="${imgId}" alt="${param.caption1}"
style="display:none">
<c:if test="${param.showLink or param.showZoom}"></a></c:if>
</div>
<c:if test="${captionCount gt 0}">
<div class="caption1" style="width:${param.coverArtSize - 16}px"><a href="${targetUrl}" title="${param.caption1}">${param.caption1}</a></div>
</c:if>
<c:if test="${captionCount gt 1}">
<div class="caption2" style="width:${param.coverArtSize - 16}px">${param.caption2}&nbsp;</div>
</c:if>
<c:if test="${captionCount gt 2}">
<div class="caption3" style="width:${param.coverArtSize - 16}px">${param.caption3}&nbsp;</div>
</c:if>
</div>
<c:if test="${param.showChange or param.showZoom}">
<div style="padding-top:6px;text-align:right">
<c:if test="${param.showChange}">
<c:url value="/changeCoverArt.view" var="changeCoverArtUrl">
<c:param name="id" value="${param.albumId}"/>
</c:url>
<a class="detail" href="${changeCoverArtUrl}"><fmt:message key="coverart.change"/></a>
</c:if>
<c:if test="${param.showZoom and param.showChange}">
|
</c:if>
<c:if test="${param.showZoom}">
<a class="detail" rel="zoom" title="${param.caption1}" href="${zoomCoverArtUrl}"><fmt:message key="coverart.zoom"/></a>
</c:if>
</div>
</c:if>
<script type="text/javascript">
$(document).ready(function () {
setTimeout("$('#${imgId}').fadeIn(500)", ${empty param.appearAfter ? 0 : param.appearAfter});
});
$("#${divId}").mouseenter(function () {
$("#${playId}").show();
$("#${imgId}").stop();
$("#${imgId}").animate({opacity: 0.7}, 150);
});
$("#${divId}").mouseleave(function () {
$("#${playId}").hide();
$("#${imgId}").stop();
$("#${imgId}").animate({opacity: 1.0}, 150);
});
$("#${playId}").click(function () {
<c:if test="${not empty param.albumId}">
top.playQueue.onPlay(${param.albumId});
</c:if>
<c:if test="${not empty param.playlistId}">
top.playQueue.onPlayPlaylist(${param.playlistId});
</c:if>
<c:if test="${not empty param.podcastChannelId}">
top.playQueue.onPlayPodcastChannel(${param.podcastChannelId});
</c:if>
});
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Loading…
Cancel
Save