diff --git a/frontend/src/Retag/RetagPreviewModalContentConnector.js b/frontend/src/Retag/RetagPreviewModalContentConnector.js
index 0e7255eb5..ce3a64776 100644
--- a/frontend/src/Retag/RetagPreviewModalContentConnector.js
+++ b/frontend/src/Retag/RetagPreviewModalContentConnector.js
@@ -75,7 +75,6 @@ class RetagPreviewModalContentConnector extends Component {
RetagPreviewModalContentConnector.propTypes = {
artistId: PropTypes.number.isRequired,
albumId: PropTypes.number,
- retagTracks: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
fetchRetagPreview: PropTypes.func.isRequired,
diff --git a/frontend/src/Retag/RetagPreviewRow.js b/frontend/src/Retag/RetagPreviewRow.js
index e23fe914c..e02246253 100644
--- a/frontend/src/Retag/RetagPreviewRow.js
+++ b/frontend/src/Retag/RetagPreviewRow.js
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
+import formatBytes from 'Utilities/Number/formatBytes';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import CheckInput from 'Components/Form/CheckInput';
@@ -7,16 +8,19 @@ import styles from './RetagPreviewRow.css';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
-function formatMissing(value) {
- if (value === undefined || value === 0 || value === '0') {
+function formatValue(field, value) {
+ if (value === undefined || value === 0 || value === '0' || value === '') {
return ();
}
+ if (field === 'Image Size') {
+ return formatBytes(value);
+ }
return value;
}
-function formatChange(oldValue, newValue) {
+function formatChange(field, oldValue, newValue) {
return (
-
{formatMissing(oldValue)} {formatMissing(newValue)}
+ {formatValue(field, oldValue)} {formatValue(field, newValue)}
);
}
@@ -78,7 +82,7 @@ class RetagPreviewRow extends Component {
);
})
diff --git a/src/NzbDrone.Core.Test/Files/Media/nin.mp3 b/src/NzbDrone.Core.Test/Files/Media/nin.mp3
index 081b897f3..20c5ec4ba 100644
Binary files a/src/NzbDrone.Core.Test/Files/Media/nin.mp3 and b/src/NzbDrone.Core.Test/Files/Media/nin.mp3 differ
diff --git a/src/NzbDrone.Core.Test/Files/Media/nin.png b/src/NzbDrone.Core.Test/Files/Media/nin.png
new file mode 100644
index 000000000..9b0164982
Binary files /dev/null and b/src/NzbDrone.Core.Test/Files/Media/nin.png differ
diff --git a/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
index 8a4b43a46..a10476cdb 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
{
private static readonly string[] MediaFiles = new [] { "nin.mp2", "nin.mp3", "nin.flac", "nin.m4a", "nin.wma", "nin.ape", "nin.opus" };
- private static readonly string[] SkipProperties = new [] { "IsValid", "Duration", "Quality", "MediaInfo" };
+ private static readonly string[] SkipProperties = new [] { "IsValid", "Duration", "Quality", "MediaInfo", "ImageFile" };
private static readonly Dictionary SkipPropertiesByFile = new Dictionary {
{ "nin.mp2", new [] {"OriginalReleaseDate"} }
};
@@ -61,6 +61,9 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
.Setup(x => x.WriteAudioTags)
.Returns(WriteAudioTagsType.Sync);
+ var imageFile = Path.Combine(testdir, "nin.png");
+ var imageSize = _diskProvider.GetFileSize(imageFile);
+
// have to manually set the arrays of string parameters and integers to values > 1
testTags = Builder.CreateNew()
.With(x => x.Track = 2)
@@ -73,6 +76,9 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
.With(x => x.OriginalYear = 2009)
.With(x => x.Performers = new [] { "Performer1" })
.With(x => x.AlbumArtists = new [] { "방탄소년단" })
+ .With(x => x.Genres = new [] { "Genre1", "Genre2" })
+ .With(x => x.ImageFile = imageFile)
+ .With(x => x.ImageSize = imageSize)
.Build();
}
@@ -228,7 +234,8 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
var tag = Subject.ReadAudioTag(path);
var expected = new AudioTag() {
Performers = new string[0],
- AlbumArtists = new string[0]
+ AlbumArtists = new string[0],
+ Genres = new string[0]
};
VerifySame(tag, expected, skipProperties);
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index a373e07d7..8b88ba4d4 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -471,6 +471,9 @@
Always
+
+ Always
+
Always
@@ -639,4 +642,4 @@
-
\ No newline at end of file
+
diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
index ec90e4cfb..108714142 100644
--- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
+++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaCover
{
void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable covers);
string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCoverTypes mediaCoverTypes, string extension, int? height = null);
+ void EnsureAlbumCovers(Album album);
}
public class MediaCoverService :
@@ -137,7 +138,7 @@ namespace NzbDrone.Core.MediaCover
}
}
- private void EnsureAlbumCovers(Album album)
+ public void EnsureAlbumCovers(Album album)
{
foreach (var cover in album.Images.Where(e => e.CoverType == MediaCoverTypes.Cover))
{
diff --git a/src/NzbDrone.Core/MediaFiles/AudioTag.cs b/src/NzbDrone.Core/MediaFiles/AudioTag.cs
index c0475a66f..40d7eb996 100644
--- a/src/NzbDrone.Core/MediaFiles/AudioTag.cs
+++ b/src/NzbDrone.Core/MediaFiles/AudioTag.cs
@@ -34,6 +34,9 @@ namespace NzbDrone.Core.MediaFiles
public uint OriginalYear { get; set; }
public string Publisher { get; set; }
public TimeSpan Duration { get; set; }
+ public string[] Genres { get; set; }
+ public string ImageFile { get; set; }
+ public long ImageSize { get; set; }
public string MusicBrainzReleaseCountry { get; set; }
public string MusicBrainzReleaseStatus { get; set; }
public string MusicBrainzReleaseType { get; set; }
@@ -81,6 +84,8 @@ namespace NzbDrone.Core.MediaFiles
Year = tag.Year;
Publisher = tag.Publisher;
Duration = file.Properties.Duration;
+ Genres = tag.Genres;
+ ImageSize = tag.Pictures.FirstOrDefault()?.Data.Count ?? 0;
MusicBrainzReleaseCountry = tag.MusicBrainzReleaseCountry;
MusicBrainzReleaseStatus = tag.MusicBrainzReleaseStatus;
MusicBrainzReleaseType = tag.MusicBrainzReleaseType;
@@ -315,6 +320,7 @@ namespace NzbDrone.Core.MediaFiles
// WMA with null performers/albumartists
Performers = Performers ?? new string[0];
AlbumArtists = AlbumArtists ?? new string[0];
+ Genres = Genres ?? new string[0];
TagLib.File file = null;
try
@@ -332,6 +338,7 @@ namespace NzbDrone.Core.MediaFiles
tag.Disc = Disc;
tag.DiscCount = DiscCount;
tag.Publisher = Publisher;
+ tag.Genres = Genres;
tag.MusicBrainzReleaseCountry = MusicBrainzReleaseCountry;
tag.MusicBrainzReleaseStatus = MusicBrainzReleaseStatus;
tag.MusicBrainzReleaseType = MusicBrainzReleaseType;
@@ -341,6 +348,11 @@ namespace NzbDrone.Core.MediaFiles
tag.MusicBrainzReleaseGroupId = MusicBrainzReleaseGroupId;
tag.MusicBrainzTrackId = MusicBrainzTrackId;
+ if (ImageFile.IsNotNullOrWhiteSpace())
+ {
+ tag.Pictures = new IPicture[1] { new Picture(ImageFile) };
+ }
+
if (file.TagTypes.HasFlag(TagTypes.Id3v2))
{
var id3tag = (TagLib.Id3v2.Tag) file.GetTag(TagTypes.Id3v2);
@@ -524,6 +536,16 @@ namespace NzbDrone.Core.MediaFiles
output.Add("Label", Tuple.Create(Publisher, other.Publisher));
}
+ if (!Genres.SequenceEqual(other.Genres))
+ {
+ output.Add("Genres", Tuple.Create(string.Join(", ", Genres), string.Join(", ", other.Genres)));
+ }
+
+ if (ImageSize != other.ImageSize)
+ {
+ output.Add("Image Size", Tuple.Create(ImageSize.ToString(), other.ImageSize.ToString()));
+ }
+
return output;
}
diff --git a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
index 36f053239..8d660589a 100644
--- a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
+++ b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
@@ -16,6 +16,7 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using TagLib;
using NzbDrone.Common.Extensions;
+using NzbDrone.Core.MediaCover;
namespace NzbDrone.Core.MediaFiles
{
@@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly IArtistService _artistService;
+ private readonly IMapCoversToLocal _mediaCoverService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@@ -47,6 +49,7 @@ namespace NzbDrone.Core.MediaFiles
IMediaFileService mediaFileService,
IDiskProvider diskProvider,
IArtistService artistService,
+ IMapCoversToLocal mediaCoverService,
IEventAggregator eventAggregator,
Logger logger)
{
@@ -54,6 +57,7 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_artistService = artistService;
+ _mediaCoverService = mediaCoverService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@@ -76,6 +80,23 @@ namespace NzbDrone.Core.MediaFiles
var albumartist = album.Artist.Value;
var artist = track.ArtistMetadata.Value;
+ var cover = album.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover);
+ string imageFile = null;
+ long imageSize = 0;
+ if (cover != null)
+ {
+ imageFile = _mediaCoverService.GetCoverPath(album.Id, MediaCoverEntity.Album, cover.CoverType, cover.Extension, null);
+ var fileInfo = _diskProvider.GetFileInfo(imageFile);
+ if (fileInfo.Exists)
+ {
+ imageSize = fileInfo.Length;
+ }
+ else
+ {
+ imageFile = null;
+ }
+ }
+
return new AudioTag {
Title = track.Title,
Performers = new [] { artist.Name },
@@ -91,6 +112,9 @@ namespace NzbDrone.Core.MediaFiles
OriginalReleaseDate = album.ReleaseDate,
OriginalYear = (uint)album.ReleaseDate?.Year,
Publisher = release.Label.FirstOrDefault(),
+ Genres = album.Genres.Any() ? album.Genres.ToArray() : artist.Genres.ToArray(),
+ ImageFile = imageFile,
+ ImageSize = imageSize,
MusicBrainzReleaseCountry = IsoCountries.Find(release.Country.FirstOrDefault())?.TwoLetterCode,
MusicBrainzReleaseStatus = release.Status.ToLower(),
MusicBrainzReleaseType = album.AlbumType.ToLower(),
diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs
index f643299c0..3efde5a86 100644
--- a/src/NzbDrone.Core/Music/RefreshAlbumService.cs
+++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs
@@ -12,6 +12,7 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Music.Commands;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.History;
+using NzbDrone.Core.MediaCover;
namespace NzbDrone.Core.Music
{
@@ -33,6 +34,7 @@ namespace NzbDrone.Core.Music
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly ICheckIfAlbumShouldBeRefreshed _checkIfAlbumShouldBeRefreshed;
+ private readonly IMapCoversToLocal _mediaCoverService;
private readonly Logger _logger;
public RefreshAlbumService(IAlbumService albumService,
@@ -46,6 +48,7 @@ namespace NzbDrone.Core.Music
IHistoryService historyService,
IEventAggregator eventAggregator,
ICheckIfAlbumShouldBeRefreshed checkIfAlbumShouldBeRefreshed,
+ IMapCoversToLocal mediaCoverService,
Logger logger)
: base(logger, artistMetadataService)
{
@@ -59,6 +62,7 @@ namespace NzbDrone.Core.Music
_historyService = historyService;
_eventAggregator = eventAggregator;
_checkIfAlbumShouldBeRefreshed = checkIfAlbumShouldBeRefreshed;
+ _mediaCoverService = mediaCoverService;
_logger = logger;
}
@@ -155,6 +159,13 @@ namespace NzbDrone.Core.Music
{
result = UpdateResult.None;
}
+
+ // Force update and fetch covers if images have changed so that we can write them into tags
+ if (remote.Images.Any() && !local.Images.Select(x => x.Url).SequenceEqual(remote.Images.Select(x => x.Url)))
+ {
+ _mediaCoverService.EnsureAlbumCovers(remote);
+ result = UpdateResult.UpdateTags;
+ }
local.ArtistMetadataId = remote.ArtistMetadata.Value.Id;
local.ForeignAlbumId = remote.ForeignAlbumId;