mirror of https://github.com/lidarr/Lidarr
Fixed: Cache Album covers local (#780)
* Fixed: Cache Album covers local * Fixed: Maxsize is handled by the backend mapping * Fixed: Store Album covers seperate from ArtistId
This commit is contained in:
parent
bcdef2723f
commit
35c19dac5f
|
@ -1,175 +1,25 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LazyLoad from 'react-lazyload';
|
||||
import React from 'react';
|
||||
import ArtistImage from 'Artist/ArtistImage';
|
||||
|
||||
const coverPlaceholder = 'data:image/png;base64,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';
|
||||
|
||||
function findCover(images) {
|
||||
return _.find(images, { coverType: 'cover' });
|
||||
}
|
||||
|
||||
function getCoverUrl(cover, size) {
|
||||
if (cover) {
|
||||
if (cover.url.contains('lastWrite=') || (/^https?:/).test(cover.url)) {
|
||||
// Remove protocol
|
||||
let url = cover.url.replace(/^https?:/, '');
|
||||
url = url.replace('cover.jpg', `cover-${size}.jpg`);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumCover extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const pixelRatio = Math.floor(window.devicePixelRatio);
|
||||
|
||||
const {
|
||||
images,
|
||||
size
|
||||
} = props;
|
||||
|
||||
const cover = findCover(images);
|
||||
|
||||
this.state = {
|
||||
pixelRatio,
|
||||
cover,
|
||||
coverUrl: getCoverUrl(cover, pixelRatio * size),
|
||||
isLoaded: false,
|
||||
hasError: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
images,
|
||||
size
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
cover,
|
||||
pixelRatio
|
||||
} = this.state;
|
||||
|
||||
const nextCover = findCover(images);
|
||||
|
||||
if (nextCover && (!cover || nextCover.url !== cover.url)) {
|
||||
this.setState({
|
||||
cover: nextCover,
|
||||
coverUrl: getCoverUrl(nextCover, pixelRatio * size),
|
||||
hasError: false,
|
||||
isLoaded: true
|
||||
});
|
||||
}
|
||||
|
||||
// The cover could not be loaded..
|
||||
if (!nextCover && (this.props !== prevProps)) {
|
||||
this.setState({
|
||||
cover: undefined,
|
||||
coverUrl: coverPlaceholder,
|
||||
hasError: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onError = () => {
|
||||
this.setState({ hasError: true });
|
||||
}
|
||||
|
||||
onLoad = () => {
|
||||
this.setState({
|
||||
isLoaded: true,
|
||||
hasError: false
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
size,
|
||||
lazy,
|
||||
overflow
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
coverUrl,
|
||||
hasError,
|
||||
isLoaded
|
||||
} = this.state;
|
||||
|
||||
if (hasError || !coverUrl) {
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={coverPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (lazy) {
|
||||
return (
|
||||
<LazyLoad
|
||||
height={size}
|
||||
offset={100}
|
||||
overflow={overflow}
|
||||
placeholder={
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={coverPlaceholder}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={coverUrl}
|
||||
onError={this.onError}
|
||||
/>
|
||||
</LazyLoad>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={isLoaded ? coverUrl : coverPlaceholder}
|
||||
onError={this.onError}
|
||||
onLoad={this.onLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
function AlbumCover(props) {
|
||||
return (
|
||||
<ArtistImage
|
||||
{...props}
|
||||
coverType="cover"
|
||||
placeholder={coverPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumCover.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
lazy: PropTypes.bool.isRequired,
|
||||
overflow: PropTypes.bool.isRequired
|
||||
size: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
AlbumCover.defaultProps = {
|
||||
size: 250,
|
||||
lazy: true,
|
||||
overflow: false
|
||||
size: 250
|
||||
};
|
||||
|
||||
export default AlbumCover;
|
||||
|
|
|
@ -301,7 +301,7 @@ class AlbumDetails extends Component {
|
|||
<AlbumCover
|
||||
className={styles.cover}
|
||||
images={images}
|
||||
size={500}
|
||||
size={250}
|
||||
lazy={false}
|
||||
/>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ function getUrl(image, coverType, size) {
|
|||
if (image) {
|
||||
// Remove protocol
|
||||
let url = image.url.replace(/^https?:/, '');
|
||||
|
||||
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
||||
|
||||
return url;
|
||||
|
@ -24,7 +25,7 @@ class ArtistImage extends Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const pixelRatio = Math.floor(window.devicePixelRatio);
|
||||
const pixelRatio = Math.ceil(window.devicePixelRatio);
|
||||
|
||||
const {
|
||||
images,
|
||||
|
|
|
@ -353,7 +353,7 @@ class ArtistDetails extends Component {
|
|||
<ArtistPoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={500}
|
||||
size={250}
|
||||
lazy={false}
|
||||
/>
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
|
||||
namespace Lidarr.Api.V1.Albums
|
||||
{
|
||||
|
@ -23,13 +24,14 @@ namespace Lidarr.Api.V1.Albums
|
|||
|
||||
{
|
||||
protected readonly IReleaseService _releaseService;
|
||||
|
||||
|
||||
public AlbumModule(IAlbumService albumService,
|
||||
IReleaseService releaseService,
|
||||
IArtistStatisticsService artistStatisticsService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
IUpgradableSpecification upgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster)
|
||||
: base(albumService, artistStatisticsService, coverMapper, upgradableSpecification, signalRBroadcaster)
|
||||
{
|
||||
_releaseService = releaseService;
|
||||
GetResourceAll = GetAlbums;
|
||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Music;
|
|||
using NzbDrone.Core.ArtistStats;
|
||||
using NzbDrone.SignalR;
|
||||
using Lidarr.Http;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
|
||||
namespace Lidarr.Api.V1.Albums
|
||||
{
|
||||
|
@ -15,15 +16,18 @@ namespace Lidarr.Api.V1.Albums
|
|||
protected readonly IAlbumService _albumService;
|
||||
protected readonly IArtistStatisticsService _artistStatisticsService;
|
||||
protected readonly IUpgradableSpecification _qualityUpgradableSpecification;
|
||||
protected readonly IMapCoversToLocal _coverMapper;
|
||||
|
||||
protected AlbumModuleWithSignalR(IAlbumService albumService,
|
||||
IArtistStatisticsService artistStatisticsService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
IUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_artistStatisticsService = artistStatisticsService;
|
||||
_coverMapper = coverMapper;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
|
||||
GetResourceById = GetAlbum;
|
||||
|
@ -62,6 +66,7 @@ namespace Lidarr.Api.V1.Albums
|
|||
}
|
||||
|
||||
FetchAndLinkAlbumStatistics(resource);
|
||||
MapCoversToLocal(resource);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
@ -86,6 +91,7 @@ namespace Lidarr.Api.V1.Albums
|
|||
|
||||
var artistStats = _artistStatisticsService.ArtistStatistics();
|
||||
LinkArtistStatistics(result, artistStats);
|
||||
MapCoversToLocal(result.ToArray());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -114,5 +120,13 @@ namespace Lidarr.Api.V1.Albums
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
private void MapCoversToLocal(params AlbumResource[] albums)
|
||||
{
|
||||
foreach (var albumResource in albums)
|
||||
{
|
||||
_coverMapper.ConvertToLocalUrls(albumResource.Id, MediaCoverEntity.Album, albumResource.Images);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ namespace Lidarr.Api.V1.Artist
|
|||
{
|
||||
foreach (var artistResource in artists)
|
||||
{
|
||||
_coverMapper.ConvertToLocalUrls(artistResource.Id, artistResource.Images);
|
||||
_coverMapper.ConvertToLocalUrls(artistResource.Id, MediaCoverEntity.Artist, artistResource.Images);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ namespace Lidarr.Api.V1.MediaCovers
|
|||
{
|
||||
private static readonly Regex RegexResizedImage = new Regex(@"-\d+\.jpg$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private const string MEDIA_COVER_ROUTE = @"/(?<artistId>\d+)/(?<filename>(.+)\.(jpg|png|gif))";
|
||||
private const string MEDIA_COVER_ARTIST_ROUTE = @"/Artist/(?<artistId>\d+)/(?<filename>(.+)\.(jpg|png|gif))";
|
||||
private const string MEDIA_COVER_ALBUM_ROUTE = @"/Album/(?<artistId>\d+)/(?<filename>(.+)\.(jpg|png|gif))";
|
||||
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
@ -22,10 +23,11 @@ namespace Lidarr.Api.V1.MediaCovers
|
|||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
|
||||
Get[MEDIA_COVER_ROUTE] = options => GetMediaCover(options.artistId, options.filename);
|
||||
Get[MEDIA_COVER_ARTIST_ROUTE] = options => GetArtistMediaCover(options.artistId, options.filename);
|
||||
Get[MEDIA_COVER_ALBUM_ROUTE] = options => GetAlbumMediaCover(options.artistId, options.filename);
|
||||
}
|
||||
|
||||
private Response GetMediaCover(int artistId, string filename)
|
||||
private Response GetArtistMediaCover(int artistId, string filename)
|
||||
{
|
||||
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", artistId.ToString(), filename);
|
||||
|
||||
|
@ -43,5 +45,24 @@ namespace Lidarr.Api.V1.MediaCovers
|
|||
|
||||
return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath));
|
||||
}
|
||||
|
||||
private Response GetAlbumMediaCover(int albumId, string filename)
|
||||
{
|
||||
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", "Albums", albumId.ToString(), filename);
|
||||
|
||||
if (!_diskProvider.FileExists(filePath) || _diskProvider.GetFileSize(filePath) == 0)
|
||||
{
|
||||
// Return the full sized image if someone requests a non-existing resized one.
|
||||
// TODO: This code can be removed later once everyone had the update for a while.
|
||||
var basefilePath = RegexResizedImage.Replace(filePath, ".jpg");
|
||||
if (basefilePath == filePath || !_diskProvider.FileExists(basefilePath))
|
||||
{
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
filePath = basefilePath;
|
||||
}
|
||||
|
||||
return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.ConvertToLocalUrls(12, covers);
|
||||
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Artist, covers);
|
||||
|
||||
|
||||
covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg?lastWrite=1234");
|
||||
|
@ -75,10 +75,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.ConvertToLocalUrls(12, covers, 6);
|
||||
Subject.ConvertToLocalUrls(6, MediaCoverEntity.Album, covers);
|
||||
|
||||
|
||||
covers.Single().Url.Should().Be("/MediaCover/12/6/disc.jpg?lastWrite=1234");
|
||||
covers.Single().Url.Should().Be("/MediaCover/Albums/6/disc.jpg?lastWrite=1234");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
};
|
||||
|
||||
|
||||
Subject.ConvertToLocalUrls(12, covers);
|
||||
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Artist, covers);
|
||||
|
||||
|
||||
covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg");
|
||||
|
@ -103,6 +103,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
.Setup(v => v.AlreadyExists(It.IsAny<DateTime>(), It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(v => v.GetAlbumsByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<Album> { _album });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
@ -110,7 +114,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Subject.HandleAsync(new ArtistUpdatedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -120,6 +124,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
.Setup(v => v.AlreadyExists(It.IsAny<DateTime>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(v => v.GetAlbumsByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<Album> { _album });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
@ -127,7 +135,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Subject.HandleAsync(new ArtistUpdatedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -141,6 +149,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(v => v.GetAlbumsByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<Album> { _album });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||
.Returns(1000);
|
||||
|
@ -162,6 +174,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(v => v.GetAlbumsByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<Album> { _album });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||
.Returns(0);
|
||||
|
@ -169,7 +185,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Subject.HandleAsync(new ArtistUpdatedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -183,6 +199,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(v => v.GetAlbumsByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<Album> { _album });
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Setup(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Throws<ApplicationException>();
|
||||
|
@ -190,7 +210,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||
Subject.HandleAsync(new ArtistUpdatedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
|||
return new List<ImageFileResult>(); ;
|
||||
}
|
||||
|
||||
var source = _mediaCoverService.GetCoverPath(artist.Id, image.CoverType);
|
||||
var source = _mediaCoverService.GetCoverPath(artist.Id, MediaCoverEntity.Artist, image.CoverType);
|
||||
var destination = Path.GetFileName(artist.Path) + Path.GetExtension(source);
|
||||
|
||||
return new List<ImageFileResult>{ new ImageFileResult(destination, source) };
|
||||
|
|
|
@ -205,7 +205,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
|||
{
|
||||
foreach (var image in artist.Metadata.Value.Images)
|
||||
{
|
||||
var source = _mediaCoverService.GetCoverPath(artist.Id, image.CoverType);
|
||||
var source = _mediaCoverService.GetCoverPath(artist.Id, MediaCoverEntity.Artist, image.CoverType);
|
||||
var destination = image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(image.Url);
|
||||
if (image.CoverType == MediaCoverTypes.Poster)
|
||||
{
|
||||
|
|
|
@ -16,6 +16,12 @@ namespace NzbDrone.Core.MediaCover
|
|||
Logo = 8
|
||||
}
|
||||
|
||||
public enum MediaCoverEntity
|
||||
{
|
||||
Artist = 0,
|
||||
Album = 1
|
||||
}
|
||||
|
||||
public class MediaCover : IEmbeddedDocument
|
||||
{
|
||||
public MediaCoverTypes CoverType { get; set; }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
|
@ -16,8 +17,8 @@ namespace NzbDrone.Core.MediaCover
|
|||
{
|
||||
public interface IMapCoversToLocal
|
||||
{
|
||||
void ConvertToLocalUrls(int artistId, IEnumerable<MediaCover> covers, int? albumId = null);
|
||||
string GetCoverPath(int artistId, MediaCoverTypes mediaCoverTypes, int? height = null, int? albumId = null);
|
||||
void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable<MediaCover> covers);
|
||||
string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCoverTypes mediaCoverTypes, int? height = null);
|
||||
}
|
||||
|
||||
public class MediaCoverService :
|
||||
|
@ -58,31 +59,33 @@ namespace NzbDrone.Core.MediaCover
|
|||
_coverRootFolder = appFolderInfo.GetMediaCoverPath();
|
||||
}
|
||||
|
||||
public string GetCoverPath(int artistId, MediaCoverTypes coverTypes, int? height = null, int? albumId = null)
|
||||
public string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCoverTypes coverTypes, int? height = null)
|
||||
{
|
||||
var heightSuffix = height.HasValue ? "-" + height.ToString() : "";
|
||||
|
||||
if (albumId.HasValue)
|
||||
if (coverEntity == MediaCoverEntity.Album)
|
||||
{
|
||||
return Path.Combine(GetAlbumCoverPath(artistId, albumId.Value), coverTypes.ToString().ToLower() + heightSuffix + ".jpg");
|
||||
return Path.Combine(GetAlbumCoverPath(entityId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(GetArtistCoverPath(entityId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg");
|
||||
}
|
||||
|
||||
return Path.Combine(GetArtistCoverPath(artistId), coverTypes.ToString().ToLower() + heightSuffix + ".jpg");
|
||||
}
|
||||
|
||||
public void ConvertToLocalUrls(int artistId, IEnumerable<MediaCover> covers, int? albumId = null)
|
||||
public void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable<MediaCover> covers)
|
||||
{
|
||||
foreach (var mediaCover in covers)
|
||||
{
|
||||
var filePath = GetCoverPath(artistId, mediaCover.CoverType, null, albumId);
|
||||
var filePath = GetCoverPath(entityId, coverEntity, mediaCover.CoverType, null);
|
||||
|
||||
if (albumId.HasValue)
|
||||
if (coverEntity == MediaCoverEntity.Album)
|
||||
{
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + artistId + "/" + albumId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Albums/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + artistId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||
}
|
||||
|
||||
if (_diskProvider.FileExists(filePath))
|
||||
|
@ -98,16 +101,16 @@ namespace NzbDrone.Core.MediaCover
|
|||
return Path.Combine(_coverRootFolder, artistId.ToString());
|
||||
}
|
||||
|
||||
private string GetAlbumCoverPath(int artistId, int albumId)
|
||||
private string GetAlbumCoverPath(int albumId)
|
||||
{
|
||||
return Path.Combine(_coverRootFolder, artistId.ToString(), albumId.ToString());
|
||||
return Path.Combine(_coverRootFolder, "Albums", albumId.ToString());
|
||||
}
|
||||
|
||||
private void EnsureCovers(Artist artist)
|
||||
private void EnsureArtistCovers(Artist artist)
|
||||
{
|
||||
foreach (var cover in artist.Metadata.Value.Images)
|
||||
{
|
||||
var fileName = GetCoverPath(artist.Id, cover.CoverType);
|
||||
var fileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType);
|
||||
var alreadyExists = false;
|
||||
|
||||
try
|
||||
|
@ -134,37 +137,37 @@ namespace NzbDrone.Core.MediaCover
|
|||
}
|
||||
}
|
||||
|
||||
//TODO Decide if we want to cache album art local
|
||||
//private void EnsureAlbumCovers(Album album)
|
||||
//{
|
||||
// foreach (var cover in album.Images)
|
||||
// {
|
||||
// var fileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id);
|
||||
// var alreadyExists = false;
|
||||
// try
|
||||
// {
|
||||
// alreadyExists = _coverExistsSpecification.AlreadyExists(cover.Url, fileName);
|
||||
// if (!alreadyExists)
|
||||
// {
|
||||
// DownloadAlbumCover(album, cover);
|
||||
// }
|
||||
// }
|
||||
// catch (WebException e)
|
||||
// {
|
||||
// _logger.Warn("Couldn't download media cover for {0}. {1}", album, e.Message);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// _logger.Error(e, "Couldn't download media cover for {0}", album);
|
||||
// }
|
||||
private void EnsureAlbumCovers(Album album)
|
||||
{
|
||||
foreach (var cover in album.Images.Where(e => e.CoverType == MediaCoverTypes.Cover))
|
||||
{
|
||||
var fileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null);
|
||||
var alreadyExists = false;
|
||||
try
|
||||
{
|
||||
var lastModifiedServer = GetCoverModifiedDate(cover.Url);
|
||||
alreadyExists = _coverExistsSpecification.AlreadyExists(lastModifiedServer, fileName);
|
||||
if (!alreadyExists)
|
||||
{
|
||||
DownloadAlbumCover(album, cover, lastModifiedServer);
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn("Couldn't download media cover for {0}. {1}", album, e.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Couldn't download media cover for {0}", album);
|
||||
}
|
||||
|
||||
// EnsureResizedCovers(album.Artist, cover, !alreadyExists, album);
|
||||
// }
|
||||
//}
|
||||
EnsureResizedAlbumCovers(album, cover, !alreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadCover(Artist artist, MediaCover cover, DateTime lastModified)
|
||||
{
|
||||
var fileName = GetCoverPath(artist.Id, cover.CoverType);
|
||||
var fileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType);
|
||||
|
||||
_logger.Info("Downloading {0} for {1} {2}", cover.CoverType, artist, cover.Url);
|
||||
_httpClient.DownloadFile(cover.Url, fileName);
|
||||
|
@ -179,109 +182,70 @@ namespace NzbDrone.Core.MediaCover
|
|||
}
|
||||
}
|
||||
|
||||
//private void DownloadAlbumCover(Album album, MediaCover cover)
|
||||
//{
|
||||
// var fileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id);
|
||||
private void DownloadAlbumCover(Album album, MediaCover cover, DateTime lastModified)
|
||||
{
|
||||
var fileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null);
|
||||
|
||||
// _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, album, cover.Url);
|
||||
// _httpClient.DownloadFile(cover.Url, fileName);
|
||||
//}
|
||||
_logger.Info("Downloading {0} for {1} {2}", cover.CoverType, album, cover.Url);
|
||||
_httpClient.DownloadFile(cover.Url, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
_diskProvider.FileSetLastWriteTime(fileName, lastModified);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to set modified date for {0} image for album {1}", cover.CoverType, album);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureResizedCovers(Artist artist, MediaCover cover, bool forceResize, Album album = null)
|
||||
{
|
||||
int[] heights;
|
||||
int[] heights = GetDefaultHeights(cover.CoverType);
|
||||
|
||||
switch (cover.CoverType)
|
||||
foreach (var height in heights)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
var mainFileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType);
|
||||
var resizeFileName = GetCoverPath(artist.Id, MediaCoverEntity.Artist, cover.CoverType, height);
|
||||
|
||||
case MediaCoverTypes.Poster:
|
||||
case MediaCoverTypes.Cover:
|
||||
case MediaCoverTypes.Disc:
|
||||
case MediaCoverTypes.Logo:
|
||||
case MediaCoverTypes.Headshot:
|
||||
heights = new[] { 500, 250 };
|
||||
break;
|
||||
|
||||
case MediaCoverTypes.Banner:
|
||||
heights = new[] { 70, 35 };
|
||||
break;
|
||||
|
||||
case MediaCoverTypes.Fanart:
|
||||
case MediaCoverTypes.Screenshot:
|
||||
heights = new[] { 360, 180 };
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (album == null)
|
||||
{
|
||||
foreach (var height in heights)
|
||||
if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0)
|
||||
{
|
||||
var mainFileName = GetCoverPath(artist.Id, cover.CoverType);
|
||||
var resizeFileName = GetCoverPath(artist.Id, cover.CoverType, height);
|
||||
_logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist);
|
||||
|
||||
if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0)
|
||||
try
|
||||
{
|
||||
_logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist);
|
||||
|
||||
try
|
||||
{
|
||||
_resizer.Resize(mainFileName, resizeFileName, height);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, artist);
|
||||
}
|
||||
_resizer.Resize(mainFileName, resizeFileName, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var height in heights)
|
||||
{
|
||||
var mainFileName = GetCoverPath(album.ArtistId, cover.CoverType, null, album.Id);
|
||||
var resizeFileName = GetCoverPath(album.ArtistId, cover.CoverType, height, album.Id);
|
||||
|
||||
if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0)
|
||||
catch
|
||||
{
|
||||
_logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, artist);
|
||||
|
||||
try
|
||||
{
|
||||
_resizer.Resize(mainFileName, resizeFileName, height);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, album);
|
||||
}
|
||||
_logger.Debug("Couldn't resize media cover {0}-{1} for artist {2}, using full size image instead.", cover.CoverType, height, artist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleAsync(ArtistUpdatedEvent message)
|
||||
private void EnsureResizedAlbumCovers(Album album, MediaCover cover, bool forceResize)
|
||||
{
|
||||
EnsureCovers(message.Artist);
|
||||
int[] heights = GetDefaultHeights(cover.CoverType);
|
||||
|
||||
//Turn off for now, not using album images
|
||||
|
||||
//var albums = _albumService.GetAlbumsByArtist(message.Artist.Id);
|
||||
//foreach (Album album in albums)
|
||||
//{
|
||||
// EnsureAlbumCovers(album);
|
||||
//}
|
||||
|
||||
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist));
|
||||
}
|
||||
|
||||
public void HandleAsync(ArtistDeletedEvent message)
|
||||
{
|
||||
var path = GetArtistCoverPath(message.Artist.Id);
|
||||
if (_diskProvider.FolderExists(path))
|
||||
foreach (var height in heights)
|
||||
{
|
||||
_diskProvider.DeleteFolder(path, true);
|
||||
var mainFileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, null);
|
||||
var resizeFileName = GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, height);
|
||||
|
||||
if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0)
|
||||
{
|
||||
_logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, album);
|
||||
|
||||
try
|
||||
{
|
||||
_resizer.Resize(mainFileName, resizeFileName, height);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Debug("Couldn't resize media cover {0}-{1} for album {2}, using full size image instead.", cover.CoverType, height, album);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,5 +262,52 @@ namespace NzbDrone.Core.MediaCover
|
|||
|
||||
return lastModifiedServer;
|
||||
}
|
||||
|
||||
private int[] GetDefaultHeights(MediaCoverTypes coverType)
|
||||
{
|
||||
switch (coverType)
|
||||
{
|
||||
default:
|
||||
return new int[] { };
|
||||
|
||||
case MediaCoverTypes.Poster:
|
||||
case MediaCoverTypes.Disc:
|
||||
case MediaCoverTypes.Logo:
|
||||
case MediaCoverTypes.Headshot:
|
||||
return new[] { 500, 250 };
|
||||
|
||||
case MediaCoverTypes.Banner:
|
||||
return new[] { 70, 35 };
|
||||
|
||||
case MediaCoverTypes.Fanart:
|
||||
case MediaCoverTypes.Screenshot:
|
||||
return new[] { 360, 180 };
|
||||
case MediaCoverTypes.Cover:
|
||||
return new[] { 250 };
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleAsync(ArtistUpdatedEvent message)
|
||||
{
|
||||
EnsureArtistCovers(message.Artist);
|
||||
|
||||
var albums = _albumService.GetAlbumsByArtist(message.Artist.Id);
|
||||
foreach (Album album in albums)
|
||||
{
|
||||
EnsureAlbumCovers(album);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist));
|
||||
}
|
||||
|
||||
public void HandleAsync(ArtistDeletedEvent message)
|
||||
{
|
||||
var path = GetArtistCoverPath(message.Artist.Id);
|
||||
if (_diskProvider.FolderExists(path))
|
||||
{
|
||||
_diskProvider.DeleteFolder(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue