diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index f3e3413d42..7dbdbf5ef6 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Collections.Generic; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -145,9 +146,10 @@ namespace MediaBrowser.Controller.MediaInfo /// The cancellation token. /// if set to true [extract images]. /// if set to true [save item]. + /// The previously failed extractions. /// Task. /// - public async Task PopulateChapterImages(Video video, CancellationToken cancellationToken, bool extractImages, bool saveItem) + public async Task PopulateChapterImages(Video video, CancellationToken cancellationToken, bool extractImages, bool saveItem) { if (video.Chapters == null) { @@ -157,9 +159,10 @@ namespace MediaBrowser.Controller.MediaInfo // Can't extract images if there are no video streams if (video.MediaStreams == null || video.MediaStreams.All(m => m.Type != MediaStreamType.Video)) { - return; + return true; } + var success = true; var changesMade = false; foreach (var chapter in video.Chapters) @@ -201,6 +204,7 @@ namespace MediaBrowser.Controller.MediaInfo } catch { + success = false; break; } } @@ -216,6 +220,8 @@ namespace MediaBrowser.Controller.MediaInfo { await _libraryManager.UpdateItem(video, CancellationToken.None).ConfigureAwait(false); } + + return success; } /// diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs index 7ea819edaa..4f40cffa10 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs @@ -72,9 +72,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo if (video != null) { - await - Kernel.Instance.FFMpegManager.PopulateChapterImages(video, cancellationToken, false, false) - .ConfigureAwait(false); + await Kernel.Instance.FFMpegManager.PopulateChapterImages(video, cancellationToken, false, false).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow); diff --git a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs index b9a5cd06df..48a114704c 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.Providers.Music { get { - return "16"; + return "17"; } } @@ -106,8 +106,20 @@ namespace MediaBrowser.Controller.Providers.Music return false; } + var comparisonData = Guid.Empty; + + var artistMusicBrainzId = item.Parent.GetProviderId(MetadataProviders.Musicbrainz); + + if (!string.IsNullOrEmpty(artistMusicBrainzId)) + { + var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId); + artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml"); + + comparisonData = GetComparisonData(new FileInfo(artistXmlPath)); + } + // Refresh anytime the parent mbz id changes - if (providerInfo.Data != GetComparisonData(item.Parent.GetProviderId(MetadataProviders.Musicbrainz))) + if (providerInfo.Data != comparisonData) { return true; } @@ -119,9 +131,9 @@ namespace MediaBrowser.Controller.Providers.Music /// Gets the comparison data. /// /// Guid. - private Guid GetComparisonData(string id) + private Guid GetComparisonData(FileInfo artistXmlFileInfo) { - return string.IsNullOrEmpty(id) ? Guid.Empty : id.GetMD5(); + return artistXmlFileInfo.Exists ? (artistXmlFileInfo.FullName + artistXmlFileInfo.LastWriteTimeUtc.Ticks).GetMD5() : Guid.Empty; } /// @@ -145,59 +157,75 @@ namespace MediaBrowser.Controller.Providers.Music item.ProviderData[Id] = data; } + var comparisonData = Guid.Empty; + if (!string.IsNullOrEmpty(artistMusicBrainzId)) { - var album = (MusicAlbum)item; + var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId); + artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml"); - if (string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) + var artistXmlFileInfo = new FileInfo(artistXmlPath); + + comparisonData = GetComparisonData(artistXmlFileInfo); + + if (artistXmlFileInfo.Exists) { - album.MusicBrainzReleaseGroupId = await GetReleaseGroupId(item.GetProviderId(MetadataProviders.Musicbrainz), cancellationToken).ConfigureAwait(false); - } + var album = (MusicAlbum)item; - // If still empty there's nothing more we can do - if (!string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) - { - var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId); - artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml"); + var releaseEntryId = item.GetProviderId(MetadataProviders.Musicbrainz); - var artistXmlFileInfo = new FileInfo(artistXmlPath); - - if (artistXmlFileInfo.Exists) + // Fanart uses the release group id so we'll have to get that now using the release entry id + if (string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) { - var doc = new XmlDocument(); + album.MusicBrainzReleaseGroupId = await GetReleaseGroupId(releaseEntryId, cancellationToken).ConfigureAwait(false); + } - doc.Load(artistXmlPath); + var doc = new XmlDocument(); - cancellationToken.ThrowIfCancellationRequested(); + doc.Load(artistXmlPath); - if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.HasImage(ImageType.Disc)) + cancellationToken.ThrowIfCancellationRequested(); + + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.HasImage(ImageType.Disc)) + { + // Try try with the release entry Id, if that doesn't produce anything try the release group id + var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/cdart/@url"); + + if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) { - var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/cdart/@url"); - - var path = node != null ? node.Value : null; - - if (!string.IsNullOrEmpty(path)) - { - item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DiscFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); - } + node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/cdart/@url"); } - if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.HasImage(ImageType.Primary)) + var path = node != null ? node.Value : null; + + if (!string.IsNullOrEmpty(path)) { - var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/albumcover/@url"); + item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DiscFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + } + } - var path = node != null ? node.Value : null; + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.HasImage(ImageType.Primary)) + { + // Try try with the release entry Id, if that doesn't produce anything try the release group id + var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/albumcover/@url"); - if (!string.IsNullOrEmpty(path)) - { - item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PrimaryFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); - } + if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId)) + { + node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/albumcover/@url"); + } + + var path = node != null ? node.Value : null; + + if (!string.IsNullOrEmpty(path)) + { + item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PrimaryFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } } } + } - data.Data = GetComparisonData(artistMusicBrainzId); + data.Data = comparisonData; SetLastRefreshed(item, DateTime.UtcNow); return true; diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index 4178d4f8b2..95dc4e7aec 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -4,12 +4,14 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MoreLinq; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MoreLinq; namespace MediaBrowser.Server.Implementations.ScheduledTasks { @@ -18,6 +20,8 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// class ChapterImagesTask : IScheduledTask { + private readonly IJsonSerializer _jsonSerializer; + /// /// The _kernel /// @@ -40,18 +44,20 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// /// The new item timer. private Timer NewItemTimer { get; set; } - + /// /// Initializes a new instance of the class. /// /// The kernel. /// The log manager. /// The library manager. - public ChapterImagesTask(Kernel kernel, ILogManager logManager, ILibraryManager libraryManager) + /// The json serializer. + public ChapterImagesTask(Kernel kernel, ILogManager logManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer) { _kernel = kernel; _logger = logManager.GetLogger(GetType().Name); _libraryManager = libraryManager; + _jsonSerializer = jsonSerializer; libraryManager.ItemAdded += libraryManager_ItemAdded; libraryManager.ItemUpdated += libraryManager_ItemAdded; @@ -93,8 +99,9 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks NewItemTimer = null; } + // Limit to video files to reduce changes of ffmpeg crash dialog foreach (var item in newItems - .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) + .Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video)) .Take(1)) { try @@ -135,11 +142,34 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks var numComplete = 0; + var failHistoryPath = Path.Combine(_kernel.FFMpegManager.VideoImagesDataPath, "failures.json"); + + List previouslyFailedImages; + + try + { + previouslyFailedImages = _jsonSerializer.DeserializeFromFile>(failHistoryPath); + } + catch (FileNotFoundException) + { + previouslyFailedImages = new List(); + } + foreach (var video in videos) { cancellationToken.ThrowIfCancellationRequested(); - await _kernel.FFMpegManager.PopulateChapterImages(video, cancellationToken, true, true); + var key = video.Path + video.DateModified.Ticks; + + var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase); + + var success = await _kernel.FFMpegManager.PopulateChapterImages(video, cancellationToken, extract, true); + + if (!success) + { + previouslyFailedImages.Add(key); + _jsonSerializer.SerializeToFile(previouslyFailedImages, failHistoryPath); + } numComplete++; double percent = numComplete;