Skip to content

Commit

Permalink
Merge pull request #500 from kagemomiji/issue254-local-artist-image
Browse files Browse the repository at this point in the history
#254 Support local artist image to replace LastFM artist image
  • Loading branch information
kagemomiji committed Jun 13, 2024
2 parents 0cd65ec + 580e14c commit 7200460
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 82 deletions.
4 changes: 2 additions & 2 deletions airsonic-main/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<tomcat.server.scope>provided</tomcat.server.scope>
<!-- For fixing import/export due to https://github.com/liquibase/liquibase/issues/1598 -->
<liquibase.version>4.22.0</liquibase.version>
<logback.version>1.4.14</logback.version>
<mockito.version>5.10.0</mockito.version>
<logback.version>1.5.6</logback.version>
<mockito.version>5.12.0</mockito.version>
<mariadb.version>3.3.2</mariadb.version>
<aspectj.version>1.9.22.1</aspectj.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2014 (C) Y.Tory
Copyright 2024 (C) Y.Tory
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.airsonic.player.service.*;
import org.airsonic.player.service.podcast.PodcastDownloadClient;
import org.airsonic.player.service.search.IndexType;
import org.airsonic.player.util.NetworkUtil;
import org.airsonic.player.util.StringUtil;
import org.airsonic.player.util.Util;
import org.apache.commons.lang.StringUtils;
Expand Down Expand Up @@ -455,10 +456,12 @@ public void getArtistInfo(HttpServletRequest request, HttpServletResponse respon
result.setBiography(artistBio.getBiography());
result.setMusicBrainzId(artistBio.getMusicBrainzId());
result.setLastFmUrl(artistBio.getLastFmUrl());
result.setSmallImageUrl(artistBio.getSmallImageUrl());
result.setMediumImageUrl(artistBio.getMediumImageUrl());
result.setLargeImageUrl(artistBio.getLargeImageUrl());
}
// extract base url
String baseUrl = NetworkUtil.getBaseUrl(request);
result.setSmallImageUrl(artistService.getArtistImageUrlByMediaFile(baseUrl, mediaFile, 34));
result.setMediumImageUrl(artistService.getArtistImageUrlByMediaFile(baseUrl, mediaFile, 64));
result.setLargeImageUrl(artistService.getArtistImageUrlByMediaFile(baseUrl, mediaFile, 300));

Response res = createResponse();
res.setArtistInfo(result);
Expand Down Expand Up @@ -492,11 +495,11 @@ public void getArtistInfo2(HttpServletRequest request, HttpServletResponse respo
result.setBiography(artistBio.getBiography());
result.setMusicBrainzId(artistBio.getMusicBrainzId());
result.setLastFmUrl(artistBio.getLastFmUrl());
result.setSmallImageUrl(artistBio.getSmallImageUrl());
result.setMediumImageUrl(artistBio.getMediumImageUrl());
result.setLargeImageUrl(artistBio.getLargeImageUrl());
}

String baseUrl = NetworkUtil.getBaseUrl(request);
result.setSmallImageUrl(artistService.getArtistImageURL(baseUrl, artist.getName(), 34));
result.setMediumImageUrl(artistService.getArtistImageURL(baseUrl, artist.getName(), 64));
result.setLargeImageUrl(artistService.getArtistImageURL(baseUrl, artist.getName(), 300));
Response res = createResponse();
res.setArtistInfo2(result);
jaxbWriter.writeResponse(request, response, res);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ public String getLastFmUrl() {
return lastFmUrl;
}

@Deprecated
public String getSmallImageUrl() {
return smallImageUrl;
}

@Deprecated
public String getMediumImageUrl() {
return mediumImageUrl;
}

@Deprecated
public String getLargeImageUrl() {
return largeImageUrl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package org.airsonic.player.service;

import org.airsonic.player.domain.Artist;
import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.domain.MusicFolder;
import org.airsonic.player.domain.User;
import org.airsonic.player.domain.entity.StarredArtist;
import org.airsonic.player.repository.ArtistRepository;
import org.airsonic.player.repository.OffsetBasedPageRequest;
Expand All @@ -31,10 +33,15 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Service
public class ArtistService {
Expand All @@ -43,19 +50,30 @@ public class ArtistService {

private final StarredArtistRepository starredArtistRepository;

private static final Logger LOG = LoggerFactory.getLogger(ArtistService.class);
private final JWTSecurityService jwtSecurityService;

private final SecurityService securityService;

private final MediaFileService mediaFileService;

public ArtistService(ArtistRepository artistRepository, StarredArtistRepository starredArtistRepository) {
public ArtistService(ArtistRepository artistRepository, StarredArtistRepository starredArtistRepository,
JWTSecurityService jwtSecurityService, SecurityService securityService, MediaFileService mediaFileService) {
this.artistRepository = artistRepository;
this.starredArtistRepository = starredArtistRepository;
this.jwtSecurityService = jwtSecurityService;
this.securityService = securityService;
this.mediaFileService = mediaFileService;
}

private static final Logger LOG = LoggerFactory.getLogger(ArtistService.class);

public List<Artist> getArtists(List<MusicFolder> musicFolders, int count, int offset) {
if (CollectionUtils.isEmpty(musicFolders)) {
LOG.warn("getArtists: musicFolders is null");
return Collections.emptyList();
}
return artistRepository.findByFolderInAndPresentTrue(musicFolders, new OffsetBasedPageRequest(offset, count, Sort.by("id")));
return artistRepository.findByFolderInAndPresentTrue(musicFolders,
new OffsetBasedPageRequest(offset, count, Sort.by("id")));
}

/**
Expand All @@ -66,7 +84,7 @@ public List<Artist> getArtists(List<MusicFolder> musicFolders, int count, int of
*/
public Artist getArtist(Integer id) {
if (id == null) {
LOG.warn("getArtist: id is null");
LOG.debug("getArtist: id is null");
return null;
}
return artistRepository.findById(id).orElse(null);
Expand All @@ -80,7 +98,7 @@ public Artist getArtist(Integer id) {
*/
public Artist getArtist(String artistName) {
if (!StringUtils.hasLength(artistName)) {
LOG.warn("getArtist: artistName is null");
LOG.debug("getArtist: artistName is null");
return null;
}
return artistRepository.findByName(artistName).orElse(null);
Expand All @@ -89,13 +107,13 @@ public Artist getArtist(String artistName) {
/**
* Get artist by name and music folders
*
* @param artistName artist name
* @param artistName artist name
* @param musicFolders music folders. If null or empty, return null
* @return artist dto or null
*/
public Artist getArtist(String artistName, List<MusicFolder> musicFolders) {
if (CollectionUtils.isEmpty(musicFolders) || !StringUtils.hasLength(artistName)) {
LOG.warn("getArtist: musicFolders is null or artistName is null");
LOG.debug("getArtist: musicFolders is null or artistName is null");
return null;
}
return artistRepository.findByNameAndFolderIn(artistName, musicFolders).orElse(null);
Expand All @@ -119,25 +137,27 @@ public List<Artist> getAlphabeticalArtists(final List<MusicFolder> musicFolders)
/**
* Get starred artists by username and music folders
*
* @param username username to check. If null or empty, return empty list
* @param username username to check. If null or empty, return empty list
* @param musicFolders music folders. If null or empty, return empty list
* @return list of starred artists or empty list. Sorted by starred date descending.
* @return list of starred artists or empty list. Sorted by starred date
* descending.
*/
@Transactional
public List<Artist> getStarredArtists(String username, List<MusicFolder> musicFolders) {
if (CollectionUtils.isEmpty(musicFolders) || !StringUtils.hasLength(username)) {
LOG.warn("getStarredArtists: musicFolders or username is null");
return Collections.emptyList();
}
return starredArtistRepository.findByUsernameAndArtistFolderInAndArtistPresentTrue(username, musicFolders, Sort.by(Sort.Direction.DESC, "created")).stream().map(StarredArtist::getArtist).toList();
return starredArtistRepository.findByUsernameAndArtistFolderInAndArtistPresentTrue(username, musicFolders,
Sort.by(Sort.Direction.DESC, "created")).stream().map(StarredArtist::getArtist).toList();
}

/**
* Star or unstar artist
*
* @param artistId artist id
* @param username username to star artist for
* @param star true to star, false to unstar
* @param star true to star, false to unstar
* @return true if success, false otherwise
*/
@Transactional
Expand Down Expand Up @@ -169,7 +189,8 @@ public Instant getStarredDate(Integer artistId, String username) {
LOG.warn("getStarredDate: artistId or username is null");
return null;
}
return starredArtistRepository.findByArtistIdAndUsername(artistId, username).map(StarredArtist::getCreated).orElse(null);
return starredArtistRepository.findByArtistIdAndUsername(artistId, username).map(StarredArtist::getCreated)
.orElse(null);
}

/**
Expand Down Expand Up @@ -204,4 +225,81 @@ public Artist save(Artist artist) {
return artist;
}

/**
* Get artist image URL
*
* @param baseUrl base url
* @param artistName artist name
* @param size image size
* @return image url or null. If artist name is null or empty, return null.
*/
@Nullable
@Transactional
public String getArtistImageURL(@Nonnull String baseUrl, @Nullable String artistName, int size) {
if (!StringUtils.hasLength(artistName)) {
LOG.debug("getArtistImageURL: artistName is null");
return null;
}
String prefix = "ext";
// expire in 5 minutes
Instant expires = Instant.now().plusSeconds(300);
return artistRepository.findByName(artistName).map(artist -> {
securityService.createGuestUserIfNotExists();
return baseUrl + jwtSecurityService.addJWTToken(
User.USERNAME_GUEST,
UriComponentsBuilder
.fromUriString(prefix + "/coverArt.view")
.queryParam("id", String.format("ar-%d", artist.getId()))
.queryParam("size", String.valueOf(size)),
expires)
.build().toUriString();
}).orElse(null);

}

@Nullable
@Transactional
public String getArtistImageUrlByMediaFile(@Nonnull String baseUrl, @Nullable MediaFile mediaFile, int size) {

if (mediaFile == null) {
LOG.debug("getArtistImageUrlByMediaFile: mediaFile is null");
return null;
}

Integer mediaFileId = mediaFile.getId();

// if media file is audio or album, get artist image url by album artist or
// artist
if (mediaFile.isAudio() || mediaFile.isAlbum()) {
// get artist image url by album artist or artist
String url = Optional.ofNullable(getArtistImageURL(baseUrl, mediaFile.getAlbumArtist(), size))
.orElse(getArtistImageURL(baseUrl, mediaFile.getArtist(), size));
if (url != null)
return url;

if (mediaFile.isAudio()) {
mediaFile = mediaFileService.getParentOf(mediaFile, true);
}
MediaFile artistFile = mediaFileService.getParentOf(mediaFile, true);
if (artistFile == null) {
LOG.debug("getArtistImageUrlByMediaFile: artistFile is null");
return null;
}
mediaFileId = artistFile.getId();
}

// generate artist image url by media file id
String prefix = "ext";
// expire in 5 minutes
Instant expires = Instant.now().plusSeconds(300);
securityService.createGuestUserIfNotExists();
return baseUrl + jwtSecurityService.addJWTToken(
User.USERNAME_GUEST,
UriComponentsBuilder
.fromUriString(prefix + "/coverArt.view")
.queryParam("id", String.valueOf(mediaFileId))
.queryParam("size", String.valueOf(size)),
expires)
.build().toUriString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ public void upsert(CoverArt art) {
*
* @param mediaFile the media file
*/
@Transactional
public void persistIfNeeded(MediaFile mediaFile) {
CoverArt mediaFileArt = mediaFile.getArt();
if (mediaFileArt != null && !CoverArt.NULL_ART.equals(mediaFileArt)) {
CoverArt art = getMediaFileArt(mediaFile.getId());
CoverArt art = coverArtRepository.findByEntityTypeAndEntityId(EntityType.MEDIA_FILE, mediaFile.getId()).orElse(CoverArt.NULL_ART);
if (CoverArt.NULL_ART.equals(art) || !art.getOverridden()) {
mediaFileArt.setEntityId(mediaFile.getId());
mediaFileArt.setEntityType(EntityType.MEDIA_FILE);
Expand All @@ -76,10 +77,11 @@ public void persistIfNeeded(MediaFile mediaFile) {
*
* @param album the album
*/
@Transactional
public void persistIfNeeded(Album album) {
CoverArt albumArt = album.getArt();
if (albumArt != null && !CoverArt.NULL_ART.equals(albumArt)) {
CoverArt art = getAlbumArt(album.getId());
CoverArt art = coverArtRepository.findByEntityTypeAndEntityId(EntityType.ALBUM, album.getId()).orElse(CoverArt.NULL_ART);
if (CoverArt.NULL_ART.equals(art) || !art.getOverridden()) {
albumArt.setEntityId(album.getId());
albumArt.setEntityType(EntityType.ALBUM);
Expand All @@ -94,10 +96,11 @@ public void persistIfNeeded(Album album) {
*
* @param artist
*/
@Transactional
public void persistIfNeeded(Artist artist) {
CoverArt artistArt = artist.getArt();
if (artistArt != null && !CoverArt.NULL_ART.equals(artistArt)) {
CoverArt art = getArtistArt(artist.getId());
CoverArt art = coverArtRepository.findByEntityTypeAndEntityId(EntityType.ARTIST, artist.getId()).orElse(CoverArt.NULL_ART);
if (CoverArt.NULL_ART.equals(art) || !art.getOverridden()) {
artistArt.setEntityId(artist.getId());
artistArt.setEntityType(EntityType.ARTIST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,14 @@ private ArtistBio getArtistBio(String artistName, Locale locale) {
if (info == null) {
return null;
}

// image urls are deprecated
return new ArtistBio(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 artist bio for " + artistName, x);
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1051,16 +1051,15 @@ private MediaFile updateMediaFileByFile(MediaFile mediaFile, boolean isCheckedEx
mediaFile.setYear(metaData.getYear());
mediaFile.setGenre(metaData.getGenre());
}

// Look for cover art.
Path coverArt = findCoverArt(children);
if (coverArt != null) {
// placeholder to be persisted later
mediaFile.setArt(new CoverArt(-1, EntityType.MEDIA_FILE, folder.getPath().relativize(coverArt).toString(), folder, false));
}
} else {
mediaFile.setArtist(file.getFileName().toString());
}
// Look for cover art.
Path coverArt = findCoverArt(children);
if (coverArt != null) {
// placeholder to be persisted later
mediaFile.setArt(new CoverArt(-1, EntityType.MEDIA_FILE, folder.getPath().relativize(coverArt).toString(), folder, false));
}

} catch (IOException e) {
LOG.warn("Could not retrieve children for {}.", file.toString(), e);
Expand Down
Loading

0 comments on commit 7200460

Please sign in to comment.