diff options
| author | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
|---|---|---|
| committer | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
| commit | 48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch) | |
| tree | 8dae77a31670a888d733484cb17dd4077d5444e8 /MediaBrowser.Providers | |
| parent | c32d8656382a0eacb301692e0084377fc433ae9b (diff) | |
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'MediaBrowser.Providers')
77 files changed, 2155 insertions, 1967 deletions
diff --git a/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs b/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs deleted file mode 100644 index 09546e4b61..0000000000 --- a/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs +++ /dev/null @@ -1,41 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Providers.Manager; -using System.Collections.Generic; -using System.Linq; - -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Providers.Books -{ - public class AudioPodcastMetadataService : MetadataService<AudioPodcast, SongInfo> - { - protected override void MergeData(MetadataResult<AudioPodcast> source, MetadataResult<AudioPodcast> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - var sourceItem = source.Item; - var targetItem = target.Item; - - if (replaceData || targetItem.Artists.Length == 0) - { - targetItem.Artists = sourceItem.Artists; - } - - if (replaceData || string.IsNullOrEmpty(targetItem.Album)) - { - targetItem.Album = sourceItem.Album; - } - } - - public AudioPodcastMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/Books/GoogleBooksProvider.cs b/MediaBrowser.Providers/Books/GoogleBooksProvider.cs deleted file mode 100644 index 7330b8c43e..0000000000 --- a/MediaBrowser.Providers/Books/GoogleBooksProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Books -{ - public class GoogleBooksProvider : IRemoteMetadataProvider<AudioBook, SongInfo> - { - public string Name => "Google Books"; - private readonly IHttpClient _httpClient; - - public GoogleBooksProvider(IHttpClient httpClient) - { - _httpClient = httpClient; - } - - public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url, - BufferContent = false - }); - } - - public async Task<MetadataResult<AudioBook>> GetMetadata(SongInfo info, CancellationToken cancellationToken) - { - return new MetadataResult<AudioBook>(); - } - - public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SongInfo searchInfo, CancellationToken cancellationToken) - { - return new List<RemoteSearchResult>(); - } - } -} diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index 2c3dc0e31d..78791906a0 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -9,27 +9,15 @@ using MediaBrowser.Providers.Manager; using System.Linq; using MediaBrowser.Model.IO; using MediaBrowser.Model.Extensions; +using System.Collections.Generic; namespace MediaBrowser.Providers.BoxSets { public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo> { - protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override IList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item) { - var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); - - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) - { - if (!item.LockedFields.Contains(MetadataFields.OfficialRating)) - { - if (item.UpdateRatingToContent()) - { - updateType = updateType | ItemUpdateType.MetadataEdit; - } - } - } - - return updateType; + return item.GetLinkedChildren(); } protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) @@ -42,12 +30,59 @@ namespace MediaBrowser.Providers.BoxSets if (mergeMetadataSettings) { targetItem.LinkedChildren = sourceItem.LinkedChildren; - targetItem.Shares = sourceItem.Shares; } } + protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); + + var libraryFolderIds = item.GetLibraryFolderIds(); + + var itemLibraryFolderIds = item.LibraryFolderIds; + if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) + { + item.LibraryFolderIds = libraryFolderIds; + updateType |= ItemUpdateType.MetadataImport; + } + + return updateType; + } + public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { } + + protected override bool EnableUpdatingGenresFromChildren + { + get + { + return true; + } + } + + protected override bool EnableUpdatingOfficialRatingFromChildren + { + get + { + return true; + } + } + + protected override bool EnableUpdatingStudiosFromChildren + { + get + { + return true; + } + } + + protected override bool EnableUpdatingPremiereDateFromChildren + { + get + { + return true; + } + } } } diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index 7d1f2779b7..7298972904 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs @@ -33,12 +33,12 @@ namespace MediaBrowser.Providers.BoxSets get { return "TheMovieDb"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is BoxSet; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.BoxSets }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.BoxSets { var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); return GetImages(mainResult, language, tmdbImageUrl); } diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index 17b0646eda..634329177f 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.BoxSets { public class MovieDbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo> { - private const string GetCollectionInfo3 = @"https://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; + private const string GetCollectionInfo3 = MovieDbProvider.BaseMovieDbUrl + @"3/collection/{0}?api_key={1}&append_to_response=images"; internal static MovieDbBoxSetProvider Current; @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.BoxSets var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var result = new RemoteSearchResult { @@ -189,7 +189,7 @@ namespace MediaBrowser.Providers.BoxSets { using (var json = response.Content) { - mainResult = _json.DeserializeFromStream<RootObject>(json); + mainResult = await _json.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); } } @@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.BoxSets { using (var json = response.Content) { - mainResult = _json.DeserializeFromStream<RootObject>(json); + mainResult = await _json.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); } } } @@ -225,7 +225,6 @@ namespace MediaBrowser.Providers.BoxSets return mainResult; } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) { var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); @@ -235,9 +234,9 @@ namespace MediaBrowser.Providers.BoxSets if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs index 1bbc6fa4ea..3d0c7c964a 100644 --- a/MediaBrowser.Providers/Chapters/ChapterManager.cs +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -33,11 +33,6 @@ namespace MediaBrowser.Providers.Chapters _itemRepo = itemRepo; } - public List<ChapterInfo> GetChapters(string itemId) - { - return _itemRepo.GetChapters(new Guid(itemId)); - } - public void SaveChapters(string itemId, List<ChapterInfo> chapters) { _itemRepo.SaveChapters(new Guid(itemId), chapters); diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index 1dab086715..e472a23c4b 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -24,17 +24,4 @@ namespace MediaBrowser.Providers.Folders { } } - - public class ManualCollectionsFolderMetadataService : MetadataService<ManualCollectionsFolder, ItemLookupInfo> - { - protected override void MergeData(MetadataResult<ManualCollectionsFolder> source, MetadataResult<ManualCollectionsFolder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - - public ManualCollectionsFolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } - } diff --git a/MediaBrowser.Providers/ImagesByName/ImageUtils.cs b/MediaBrowser.Providers/ImagesByName/ImageUtils.cs deleted file mode 100644 index 74c01fb5c6..0000000000 --- a/MediaBrowser.Providers/ImagesByName/ImageUtils.cs +++ /dev/null @@ -1,96 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Progress; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Providers.ImagesByName -{ - public static class ImageUtils - { - /// <summary> - /// Ensures the list. - /// </summary> - /// <param name="url">The URL.</param> - /// <param name="file">The file.</param> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="fileSystem">The file system.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public static async Task<string> EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, CancellationToken cancellationToken) - { - var fileInfo = fileSystem.GetFileInfo(file); - - if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) - { - var temp = await httpClient.GetTempFile(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Progress = new SimpleProgress<double>(), - Url = url - - }).ConfigureAwait(false); - - fileSystem.CreateDirectory(fileSystem.GetDirectoryName(file)); - - try - { - fileSystem.CopyFile(temp, file, true); - } - catch - { - - } - - return temp; - } - - return file; - } - - public static string FindMatch(IHasMetadata item, IEnumerable<string> images) - { - var name = GetComparableName(item.Name); - - return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase)); - } - - private static string GetComparableName(string name) - { - return name.Replace(" ", string.Empty) - .Replace(".", string.Empty) - .Replace("&", string.Empty) - .Replace("!", string.Empty) - .Replace(",", string.Empty) - .Replace("/", string.Empty); - } - - public static IEnumerable<string> GetAvailableImages(string file, IFileSystem fileSystem) - { - using (var fileStream = fileSystem.GetFileStream(file, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) - { - using (var reader = new StreamReader(fileStream)) - { - var lines = new List<string>(); - - while (!reader.EndOfStream) - { - var text = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(text)) - { - lines.Add(text); - } - } - - return lines; - } - } - } - } -} diff --git a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs deleted file mode 100644 index 509c911889..0000000000 --- a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Providers.Manager; -using System.Collections.Generic; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Providers.LiveTv -{ - public class AudioRecordingService : MetadataService<LiveTvAudioRecording, ItemLookupInfo> - { - protected override void MergeData(MetadataResult<LiveTvAudioRecording> source, MetadataResult<LiveTvAudioRecording> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - - public AudioRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs deleted file mode 100644 index 31e3ecaf4e..0000000000 --- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Providers.Manager; -using System.Collections.Generic; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Providers.LiveTv -{ - public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> - { - protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - - public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs index 28a12540bb..dfb0c58ad0 100644 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -5,21 +5,18 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; -using System.Collections.Generic; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.LiveTv { - public class ProgramMetadataService : MetadataService<LiveTvProgram, LiveTvProgramLookupInfo> + public class LiveTvMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> { - protected override void MergeData(MetadataResult<LiveTvProgram> source, MetadataResult<LiveTvProgram> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + public LiveTvMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { } } diff --git a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs deleted file mode 100644 index 8bfa916558..0000000000 --- a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Providers.Manager; -using System.Collections.Generic; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Providers.LiveTv -{ - public class VideoRecordingService : MetadataService<LiveTvVideoRecording, ItemLookupInfo> - { - protected override void MergeData(MetadataResult<LiveTvVideoRecording> source, MetadataResult<LiveTvVideoRecording> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - - public VideoRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 4bf5e9208b..c4d0c49293 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -39,7 +39,6 @@ namespace MediaBrowser.Providers.Manager private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - private readonly IMemoryStreamFactory _memoryStreamProvider; /// <summary> /// Initializes a new instance of the <see cref="ImageSaver" /> class. @@ -48,13 +47,12 @@ namespace MediaBrowser.Providers.Manager /// <param name="libraryMonitor">The directory watchers.</param> /// <param name="fileSystem">The file system.</param> /// <param name="logger">The logger.</param> - public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger, IMemoryStreamFactory memoryStreamProvider) + public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { _config = config; _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; - _memoryStreamProvider = memoryStreamProvider; } /// <summary> @@ -68,19 +66,19 @@ namespace MediaBrowser.Providers.Manager /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException">mimeType</exception> - public Task SaveImage(IHasMetadata item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) + public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) { return SaveImage(item, source, mimeType, type, imageIndex, null, cancellationToken); } - public async Task SaveImage(IHasMetadata item, Stream source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) + public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(mimeType)) { throw new ArgumentNullException("mimeType"); } - var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.IsOwnedItem && !(item is Audio); + var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio); if (item is User) { @@ -92,8 +90,7 @@ namespace MediaBrowser.Providers.Manager saveLocally = false; } - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) + if (!item.IsFileProtocol) { saveLocally = false; @@ -127,7 +124,7 @@ namespace MediaBrowser.Providers.Manager var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false); // If there are more than one output paths, the stream will need to be seekable - var memoryStream = _memoryStreamProvider.CreateNew(); + var memoryStream = new MemoryStream(); using (source) { await source.CopyToAsync(memoryStream).ConfigureAwait(false); @@ -236,7 +233,7 @@ namespace MediaBrowser.Providers.Manager /// <returns>Task.</returns> private async Task SaveImageToLocation(Stream source, string path, CancellationToken cancellationToken) { - _logger.Info("Saving image to {0}", path); + _logger.Debug("Saving image to {0}", path); var parentFolder = _fileSystem.GetDirectoryName(path); @@ -287,7 +284,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="mimeType">Type of the MIME.</param> /// <param name="saveLocally">if set to <c>true</c> [save locally].</param> /// <returns>IEnumerable{System.String}.</returns> - private string[] GetSavePaths(IHasMetadata item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) + private string[] GetSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) { if (!saveLocally || (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy)) { @@ -309,7 +306,7 @@ namespace MediaBrowser.Providers.Manager /// or /// imageIndex /// </exception> - private ItemImageInfo GetCurrentImage(IHasMetadata item, ImageType type, int imageIndex) + private ItemImageInfo GetCurrentImage(BaseItem item, ImageType type, int imageIndex) { return item.GetImageInfo(type, imageIndex); } @@ -324,7 +321,7 @@ namespace MediaBrowser.Providers.Manager /// <exception cref="System.ArgumentNullException">imageIndex /// or /// imageIndex</exception> - private void SetImagePath(IHasMetadata item, ImageType type, int? imageIndex, string path) + private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path) { item.SetImagePath(type, imageIndex ?? 0, _fileSystem.GetFileInfo(path)); } @@ -343,7 +340,7 @@ namespace MediaBrowser.Providers.Manager /// or /// imageIndex /// </exception> - private string GetStandardSavePath(IHasMetadata item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) + private string GetStandardSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) { var season = item as Season; var extension = MimeTypes.ToExtension(mimeType); @@ -416,7 +413,7 @@ namespace MediaBrowser.Providers.Manager filename = item is MusicAlbum ? "cdart" : "disc"; break; case ImageType.Primary: - filename = item is Episode ? _fileSystem.GetFileNameWithoutExtension(item.Path) : folderName; + filename = saveLocally && item is Episode ? _fileSystem.GetFileNameWithoutExtension(item.Path) : folderName; break; case ImageType.Backdrop: filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex); @@ -496,7 +493,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="mimeType">Type of the MIME.</param> /// <returns>IEnumerable{System.String}.</returns> /// <exception cref="System.ArgumentNullException">imageIndex</exception> - private string[] GetCompatibleSavePaths(IHasMetadata item, ImageType type, int? imageIndex, string mimeType) + private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType) { var season = item as Season; @@ -616,7 +613,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="imageFilename">The image filename.</param> /// <param name="extension">The extension.</param> /// <returns>System.String.</returns> - private string GetSavePathForItemInMixedFolder(IHasMetadata item, ImageType type, string imageFilename, string extension) + private string GetSavePathForItemInMixedFolder(BaseItem item, ImageType type, string imageFilename, string extension) { if (type == ImageType.Primary) { diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 1d432463bd..c80d438411 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -24,6 +24,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Channels; namespace MediaBrowser.Providers.Manager { @@ -42,7 +43,7 @@ namespace MediaBrowser.Providers.Manager _fileSystem = fileSystem; } - public bool ValidateImages(IHasMetadata item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService) + public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService) { var hasChanges = false; @@ -61,7 +62,7 @@ namespace MediaBrowser.Providers.Manager return hasChanges; } - public async Task<RefreshResult> RefreshImages(IHasMetadata item, LibraryOptions libraryOptions, List<IImageProvider> providers, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) + public async Task<RefreshResult> RefreshImages(BaseItem item, LibraryOptions libraryOptions, List<IImageProvider> providers, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) { if (refreshOptions.IsReplacingImage(ImageType.Backdrop)) { @@ -74,11 +75,12 @@ namespace MediaBrowser.Providers.Manager var result = new RefreshResult { UpdateType = ItemUpdateType.None }; - var providerIds = new List<Guid>(); + var typeName = item.GetType().Name; + var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName }; // In order to avoid duplicates, only download these if there are none already - var backdropLimit = savedOptions.GetLimit(ImageType.Backdrop); - var screenshotLimit = savedOptions.GetLimit(ImageType.Screenshot); + var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop); + var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot); var downloadedImages = new List<ImageType>(); foreach (var provider in providers) @@ -87,8 +89,7 @@ namespace MediaBrowser.Providers.Manager if (remoteProvider != null) { - await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); - providerIds.Add(provider.GetType().FullName.GetMD5()); + await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); continue; } @@ -96,31 +97,21 @@ namespace MediaBrowser.Providers.Manager if (dynamicImageProvider != null) { - await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, savedOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false); - providerIds.Add(provider.GetType().FullName.GetMD5()); + await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, libraryOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false); } } - result.Providers = providerIds; - return result; } /// <summary> /// Refreshes from provider. /// </summary> - /// <param name="item">The item.</param> - /// <param name="provider">The provider.</param> - /// <param name="refreshOptions">The refresh options.</param> - /// <param name="savedOptions">The saved options.</param> - /// <param name="downloadedImages">The downloaded images.</param> - /// <param name="result">The result.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task RefreshFromProvider(IHasMetadata item, + private async Task RefreshFromProvider(BaseItem item, IDynamicImageProvider provider, ImageRefreshOptions refreshOptions, - MetadataOptions savedOptions, + TypeOptions savedOptions, + LibraryOptions libraryOptions, ICollection<ImageType> downloadedImages, RefreshResult result, CancellationToken cancellationToken) @@ -202,7 +193,7 @@ namespace MediaBrowser.Providers.Manager ImageType.Thumb }; - private bool HasImage(IHasMetadata item, ImageType type) + private bool HasImage(BaseItem item, ImageType type) { return item.HasImage(type); } @@ -216,7 +207,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="backdropLimit">The backdrop limit.</param> /// <param name="screenshotLimit">The screenshot limit.</param> /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns> - private bool ContainsImages(IHasMetadata item, List<ImageType> images, MetadataOptions savedOptions, int backdropLimit, int screenshotLimit) + private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit, int screenshotLimit) { if (_singularImages.Any(i => images.Contains(i) && !HasImage(item, i) && savedOptions.GetLimit(i) > 0)) { @@ -249,10 +240,10 @@ namespace MediaBrowser.Providers.Manager /// <param name="result">The result.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task RefreshFromProvider(IHasMetadata item, LibraryOptions libraryOptions, + private async Task RefreshFromProvider(BaseItem item, LibraryOptions libraryOptions, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, - MetadataOptions savedOptions, + TypeOptions savedOptions, int backdropLimit, int screenshotLimit, ICollection<ImageType> downloadedImages, @@ -267,7 +258,7 @@ namespace MediaBrowser.Providers.Manager } if (!refreshOptions.ReplaceAllImages && - refreshOptions.ReplaceImages.Count == 0 && + refreshOptions.ReplaceImages.Length == 0 && ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit, screenshotLimit)) { return; @@ -302,20 +293,14 @@ namespace MediaBrowser.Providers.Manager } } - if (!item.LockedFields.Contains(MetadataFields.Backdrops)) - { - minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); - await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - } + minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); + await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - if (!item.LockedFields.Contains(MetadataFields.Screenshots)) + var hasScreenshots = item as IHasScreenshots; + if (hasScreenshots != null) { - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots != null) - { - minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); - await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - } + minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); + await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -329,34 +314,12 @@ namespace MediaBrowser.Providers.Manager } } - private bool IsEnabled(MetadataOptions options, ImageType type, IHasMetadata item) + private bool IsEnabled(TypeOptions options, ImageType type, BaseItem item) { - if (type == ImageType.Backdrop) - { - if (item.LockedFields.Contains(MetadataFields.Backdrops)) - { - return false; - } - } - else if (type == ImageType.Screenshot) - { - if (item.LockedFields.Contains(MetadataFields.Screenshots)) - { - return false; - } - } - else - { - if (item.LockedFields.Contains(MetadataFields.Images)) - { - return false; - } - } - return options.IsEnabled(type); } - private void ClearImages(IHasMetadata item, ImageType type) + private void ClearImages(BaseItem item, ImageType type) { var deleted = false; var deletedImages = new List<ItemImageInfo>(); @@ -376,7 +339,7 @@ namespace MediaBrowser.Providers.Manager } catch (FileNotFoundException) { - + } } @@ -388,7 +351,7 @@ namespace MediaBrowser.Providers.Manager } } - public bool MergeImages(IHasMetadata item, List<LocalImageInfo> images) + public bool MergeImages(BaseItem item, List<LocalImageInfo> images) { var changed = false; @@ -457,7 +420,7 @@ namespace MediaBrowser.Providers.Manager return changed; } - private bool UpdateMultiImages(IHasMetadata item, List<LocalImageInfo> images, ImageType type) + private bool UpdateMultiImages(BaseItem item, List<LocalImageInfo> images, ImageType type) { var changed = false; @@ -475,7 +438,7 @@ namespace MediaBrowser.Providers.Manager return changed; } - private async Task<bool> DownloadImage(IHasMetadata item, LibraryOptions libraryOptions, + private async Task<bool> DownloadImage(BaseItem item, LibraryOptions libraryOptions, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, @@ -521,14 +484,14 @@ namespace MediaBrowser.Providers.Manager return false; } - private bool EnableImageStub(IHasMetadata item, ImageType type, LibraryOptions libraryOptions) + private bool EnableImageStub(BaseItem item, ImageType type, LibraryOptions libraryOptions) { if (item is LiveTvProgram) { return true; } - if (item.LocationType == LocationType.Remote || item.LocationType == LocationType.Virtual) + if (!item.IsFileProtocol) { return true; } @@ -555,14 +518,14 @@ namespace MediaBrowser.Providers.Manager return true; } - private void SaveImageStub(IHasMetadata item, ImageType imageType, IEnumerable<string> urls) + private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls) { var newIndex = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0; SaveImageStub(item, imageType, urls, newIndex); } - private void SaveImageStub(IHasMetadata item, ImageType imageType, IEnumerable<string> urls, int newIndex) + private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex) { var path = string.Join("|", urls.Take(1).ToArray()); @@ -574,7 +537,7 @@ namespace MediaBrowser.Providers.Manager }, newIndex); } - private async Task DownloadBackdrops(IHasMetadata item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken) + private async Task DownloadBackdrops(BaseItem item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken) { foreach (var image in images.Where(i => i.Type == imageType)) { diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 4e72240f29..57711d3b68 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -18,7 +18,7 @@ using MediaBrowser.Providers.MediaInfo; namespace MediaBrowser.Providers.Manager { public abstract class MetadataService<TItemType, TIdType> : IMetadataService - where TItemType : IHasMetadata, IHasLookupInfo<TIdType>, new() + where TItemType : BaseItem, IHasLookupInfo<TIdType>, new() where TIdType : ItemLookupInfo, new() { protected readonly IServerConfigurationManager ServerConfigurationManager; @@ -27,7 +27,6 @@ namespace MediaBrowser.Providers.Manager protected readonly IFileSystem FileSystem; protected readonly IUserDataManager UserDataManager; protected readonly ILibraryManager LibraryManager; - private readonly SubtitleResolver _subtitleResolver; protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) { @@ -37,11 +36,22 @@ namespace MediaBrowser.Providers.Manager FileSystem = fileSystem; UserDataManager = userDataManager; LibraryManager = libraryManager; + } - _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem); + private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService) + { + try + { + return directoryService.GetFile(path); + } + catch (Exception ex) + { + Logger.ErrorException("Error getting file {0}", ex, path); + return null; + } } - public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) + public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { var itemOfType = (TItemType)item; var config = ProviderManager.GetMetadataOptions(item); @@ -49,46 +59,22 @@ namespace MediaBrowser.Providers.Manager var updateType = ItemUpdateType.None; var requiresRefresh = false; - var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); + var libraryOptions = LibraryManager.GetLibraryOptions(item); if (!requiresRefresh && libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays) { requiresRefresh = true; } - DateTime? newDateModified = null; - if (item.LocationType == LocationType.FileSystem) - { - var file = refreshOptions.DirectoryService.GetFile(item.Path); - if (file != null) - { - newDateModified = file.LastWriteTimeUtc; - if (item.EnableRefreshOnDateModifiedChange) - { - if (newDateModified != item.DateModified) - { - Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, newDateModified, item.Id); - requiresRefresh = true; - } - } - - if (!requiresRefresh && item.SupportsLocalMetadata) - { - var video = item as Video; - - if (video != null && !video.IsPlaceHolder) - { - requiresRefresh = !video.SubtitleFiles - .SequenceEqual(_subtitleResolver.GetExternalSubtitleFiles(video, refreshOptions.DirectoryService, false), StringComparer.Ordinal); - } - } - } - } - if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) { // TODO: If this returns true, should we instead just change metadata refresh mode to Full? requiresRefresh = item.RequiresRefresh(); + + if (requiresRefresh) + { + Logger.Debug("Refreshing {0} {1} because item.RequiresRefresh() returned true", typeof(TItemType).Name, item.Path ?? item.Name); + } } var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem); @@ -108,7 +94,10 @@ namespace MediaBrowser.Providers.Manager catch (Exception ex) { localImagesFailed = true; - Logger.ErrorException("Error validating images for {0}", ex, item.Path ?? item.Name ?? "Unknown name"); + if (!(item is IItemByName)) + { + Logger.ErrorException("Error validating images for {0}", ex, item.Path ?? item.Name ?? "Unknown name"); + } } var metadataResult = new MetadataResult<TItemType> @@ -123,12 +112,12 @@ namespace MediaBrowser.Providers.Manager // Next run metadata providers if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) { - var providers = GetProviders(item, refreshOptions, isFirstRefresh, requiresRefresh) + var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh) .ToList(); if (providers.Count > 0 || isFirstRefresh || requiresRefresh) { - if (item.BeforeMetadataRefresh()) + if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata)) { updateType = updateType | ItemUpdateType.MetadataImport; } @@ -157,7 +146,7 @@ namespace MediaBrowser.Providers.Manager } // Next run remote image providers, but only if local image providers didn't throw an exception - if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly) + if (!localImagesFailed && refreshOptions.ImageRefreshMode != MetadataRefreshMode.ValidationOnly) { var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList(); @@ -173,17 +162,21 @@ namespace MediaBrowser.Providers.Manager } } - var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType); + var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType); updateType = updateType | beforeSaveResult; - if (newDateModified.HasValue) - { - item.DateModified = newDateModified.Value; - } - // Save if changes were made, or it's never been saved before if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh) { + if (item.IsFileProtocol) + { + var file = TryGetFile(item.Path, refreshOptions.DirectoryService); + if (file != null) + { + item.DateModified = file.LastWriteTimeUtc; + } + } + // If any of these properties are set then make sure the updateType is not None, just to force everything to save if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata) { @@ -200,7 +193,7 @@ namespace MediaBrowser.Providers.Manager } // Save to database - await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); + SaveItem(metadataResult, libraryOptions, updateType, cancellationToken); } await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); @@ -215,19 +208,19 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - protected async Task SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) + protected void SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) { if (result.Item.SupportsPeople && result.People != null) { - var baseItem = result.Item as BaseItem; + var baseItem = result.Item; LibraryManager.UpdatePeople(baseItem, result.People); - await SavePeopleMetadata(result.People, libraryOptions, cancellationToken).ConfigureAwait(false); + SavePeopleMetadata(result.People, libraryOptions, cancellationToken); } result.Item.UpdateToRepository(reason, cancellationToken); } - private async Task SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken) + private void SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken) { foreach (var person in people) { @@ -250,7 +243,7 @@ namespace MediaBrowser.Providers.Manager if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) { - await AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false); + AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken); saveEntity = true; updateType = updateType | ItemUpdateType.ImageUpdate; @@ -264,20 +257,20 @@ namespace MediaBrowser.Providers.Manager } } - private async Task AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) + private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) { - if (libraryOptions.DownloadImagesInAdvance) - { - try - { - await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - return; - } - catch (Exception ex) - { - Logger.ErrorException("Error in AddPersonImage", ex); - } - } + //if (libraryOptions.DownloadImagesInAdvance) + //{ + // try + // { + // await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); + // return; + // } + // catch (Exception ex) + // { + // Logger.ErrorException("Error in AddPersonImage", ex); + // } + //} personEntity.SetImage(new ItemImageInfo { @@ -286,11 +279,10 @@ namespace MediaBrowser.Providers.Manager }, 0); } - private readonly Task _cachedTask = Task.FromResult(true); protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { item.AfterMetadataRefresh(); - return _cachedTask; + return Task.CompletedTask; } /// <summary> @@ -309,12 +301,17 @@ namespace MediaBrowser.Providers.Manager return updateType; } - protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType updateType) { - var updateType = ItemUpdateType.None; + if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType)) + { + if (isFullRefresh || updateType > ItemUpdateType.None) + { + var children = GetChildrenForMetadataUpdates(item); - updateType |= SaveCumulativeRunTimeTicks(item, isFullRefresh, currentUpdateType); - updateType |= SaveDateLastMediaAdded(item, isFullRefresh, currentUpdateType); + updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType); + } + } var presentationUniqueKey = item.CreatePresentationUniqueKey(); if (!string.Equals(item.PresentationUniqueKey, presentationUniqueKey, StringComparison.Ordinal)) @@ -326,42 +323,119 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType SaveCumulativeRunTimeTicks(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + { + if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosFromChildren || EnableUpdatingOfficialRatingFromChildren) + { + return true; + } + var folder = item as Folder; + if (folder != null) + { + return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks; + } + } + + return false; + } + + protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item) + { + var folder = item as Folder; + if (folder != null) + { + return folder.GetRecursiveChildren(); + } + + return new List<BaseItem>(); + } + + protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) { var updateType = ItemUpdateType.None; if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { - var folder = item as Folder; - if (folder != null && folder.SupportsCumulativeRunTimeTicks) + updateType |= UpdateCumulativeRunTimeTicks(item, children); + updateType |= UpdateDateLastMediaAdded(item, children); + + if (EnableUpdatingPremiereDateFromChildren) { - var items = folder.GetRecursiveChildren(i => !i.IsFolder); - var ticks = items.Select(i => i.RunTimeTicks ?? 0).Sum(); + updateType |= UpdatePremiereDate(item, children); + } + + if (EnableUpdatingGenresFromChildren) + { + updateType |= UpdateGenres(item, children); + } + + if (EnableUpdatingStudiosFromChildren) + { + updateType |= UpdateStudios(item, children); + } + + if (EnableUpdatingOfficialRatingFromChildren) + { + updateType |= UpdateOfficialRating(item, children); + } + } - if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks) + return updateType; + } + + private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList<BaseItem> children) + { + var folder = item as Folder; + if (folder != null && folder.SupportsCumulativeRunTimeTicks) + { + long ticks = 0; + + foreach (var child in children) + { + if (!child.IsFolder) { - folder.RunTimeTicks = ticks; - updateType = ItemUpdateType.MetadataEdit; + ticks += (child.RunTimeTicks ?? 0); } } + + if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks) + { + folder.RunTimeTicks = ticks; + return ItemUpdateType.MetadataEdit; + } } - return updateType; + return ItemUpdateType.None; } - private ItemUpdateType SaveDateLastMediaAdded(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IList<BaseItem> children) { var updateType = ItemUpdateType.None; var folder = item as Folder; if (folder != null && folder.SupportsDateLastMediaAdded) { - var items = folder.GetRecursiveChildren(i => !i.IsFolder).Select(i => i.DateCreated).ToList(); - var date = items.Count == 0 ? (DateTime?)null : items.Max(); + DateTime dateLastMediaAdded = DateTime.MinValue; + var any = false; + + foreach (var child in children) + { + if (!child.IsFolder) + { + var childDateCreated = child.DateCreated; + if (childDateCreated > dateLastMediaAdded) + { + dateLastMediaAdded = childDateCreated; + } + any = true; + } + } - if ((!folder.DateLastMediaAdded.HasValue && date.HasValue) || folder.DateLastMediaAdded != date) + if ((!folder.DateLastMediaAdded.HasValue && any) || folder.DateLastMediaAdded != dateLastMediaAdded) { - folder.DateLastMediaAdded = date; + folder.DateLastMediaAdded = dateLastMediaAdded; updateType = ItemUpdateType.MetadataImport; } } @@ -369,14 +443,138 @@ namespace MediaBrowser.Providers.Manager return updateType; } + protected virtual bool EnableUpdatingPremiereDateFromChildren + { + get + { + return false; + } + } + protected virtual bool EnableUpdatingGenresFromChildren + { + get + { + return false; + } + } + protected virtual bool EnableUpdatingStudiosFromChildren + { + get + { + return false; + } + } + protected virtual bool EnableUpdatingOfficialRatingFromChildren + { + get + { + return false; + } + } + + private ItemUpdateType UpdatePremiereDate(TItemType item, IList<BaseItem> children) + { + var updateType = ItemUpdateType.None; + + if (children.Count == 0) + { + return updateType; + } + + var date = children.Select(i => i.PremiereDate ?? DateTime.MaxValue).Min(); + + var originalPremiereDate = item.PremiereDate; + var originalProductionYear = item.ProductionYear; + + if (date > DateTime.MinValue && date < DateTime.MaxValue) + { + item.PremiereDate = date; + item.ProductionYear = date.Year; + } + else + { + var year = children.Select(i => i.ProductionYear ?? 0).Min(); + + if (year > 0) + { + item.ProductionYear = year; + } + } + + if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) || + (originalProductionYear ?? -1) != (item.ProductionYear ?? -1)) + { + updateType = updateType | ItemUpdateType.MetadataEdit; + } + + return updateType; + } + + private ItemUpdateType UpdateGenres(TItemType item, IList<BaseItem> children) + { + var updateType = ItemUpdateType.None; + + if (!item.LockedFields.Contains(MetadataFields.Genres)) + { + var currentList = item.Genres; + + item.Genres = children.SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (currentList.Length != item.Genres.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) + { + updateType = updateType | ItemUpdateType.MetadataEdit; + } + } + + return updateType; + } + + private ItemUpdateType UpdateStudios(TItemType item, IList<BaseItem> children) + { + var updateType = ItemUpdateType.None; + + if (!item.LockedFields.Contains(MetadataFields.Studios)) + { + var currentList = item.Studios; + + item.Studios = children.SelectMany(i => i.Studios) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) + { + updateType = updateType | ItemUpdateType.MetadataEdit; + } + } + + return updateType; + } + + private ItemUpdateType UpdateOfficialRating(TItemType item, IList<BaseItem> children) + { + var updateType = ItemUpdateType.None; + + if (!item.LockedFields.Contains(MetadataFields.OfficialRating)) + { + if (item.UpdateRatingToItems(children)) + { + updateType = updateType | ItemUpdateType.MetadataEdit; + } + } + + return updateType; + } + /// <summary> /// Gets the providers. /// </summary> /// <returns>IEnumerable{`0}.</returns> - protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh) + protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh) { // Get providers to refresh - var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList(); + var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item, libraryOptions).ToList(); var metadataRefreshMode = options.MetadataRefreshMode; @@ -410,12 +608,18 @@ namespace MediaBrowser.Providers.Manager var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>() .Any(); + var anyLocalProvidersChanged = providersWithChanges.OfType<ILocalMetadataProvider>() + .Any(); + + var anyLocalPreRefreshProvidersChanged = providersWithChanges.OfType<IPreRefreshProvider>() + .Any(); + providers = providers.Where(i => { // If any provider reports a change, always run local ones as well if (i is ILocalMetadataProvider) { - return true; + return anyRemoteProvidersChanged || anyLocalProvidersChanged || anyLocalPreRefreshProvidersChanged; } // If any remote providers changed, run them all so that priorities can be honored @@ -429,7 +633,7 @@ namespace MediaBrowser.Providers.Manager return anyRemoteProvidersChanged; } - // Run custom providers if they report a change or any remote providers change + // Run custom refresh providers if they report a change or any remote providers change return anyRemoteProvidersChanged || providersWithChanges.Contains(i); }).ToList(); @@ -439,7 +643,7 @@ namespace MediaBrowser.Providers.Manager return providers; } - protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(IHasMetadata item, IEnumerable<IImageProvider> allImageProviders, ImageRefreshOptions options) + protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(BaseItem item, IEnumerable<IImageProvider> allImageProviders, ImageRefreshOptions options) { // Get providers to refresh var providers = allImageProviders.Where(i => !(i is ILocalImageProvider)).ToList(); @@ -447,7 +651,7 @@ namespace MediaBrowser.Providers.Manager var dateLastImageRefresh = item.DateLastRefreshed; // Run all if either of these flags are true - var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || dateLastImageRefresh == default(DateTime); + var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh == default(DateTime); if (!runAllProviders) { @@ -468,7 +672,7 @@ namespace MediaBrowser.Providers.Manager return providers; } - public bool CanRefresh(IHasMetadata item) + public bool CanRefresh(BaseItem item) { return item is TItemType; } @@ -487,14 +691,13 @@ namespace MediaBrowser.Providers.Manager { var refreshResult = new RefreshResult { - UpdateType = ItemUpdateType.None, - Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList() + UpdateType = ItemUpdateType.None }; var item = metadata.Item; var customProviders = providers.OfType<ICustomMetadataProvider<TItemType>>().ToList(); - var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; + var logName = !item.IsFileProtocol ? item.Name ?? item.Path : item.Path ?? item.Name; foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider)) { @@ -606,7 +809,7 @@ namespace MediaBrowser.Providers.Manager await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false); } - ImportUserData(item, userDataList, cancellationToken); + //ImportUserData(item, userDataList, cancellationToken); return refreshResult; } @@ -621,19 +824,6 @@ namespace MediaBrowser.Providers.Manager return true; } - private void ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken) - { - var hasUserData = item as IHasUserData; - - if (hasUserData != null) - { - foreach (var userData in userDataList) - { - UserDataManager.SaveUserData(userData.UserId, hasUserData, userData, UserDataSaveReason.Import, cancellationToken); - } - } - } - private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken) { Logger.Debug("Running {0} for {1}", provider.GetType().Name, logName); @@ -662,16 +852,17 @@ namespace MediaBrowser.Providers.Manager { var refreshResult = new RefreshResult(); - var results = new List<MetadataResult<TItemType>>(); + var tmpDataMerged = false; foreach (var provider in providers) { var providerName = provider.GetType().Name; Logger.Debug("Running {0} for {1}", providerName, logName); - if (id != null) + if (id != null && !tmpDataMerged) { MergeNewData(temp.Item, id); + tmpDataMerged = true; } try @@ -682,7 +873,8 @@ namespace MediaBrowser.Providers.Manager { result.Provider = provider.Name; - results.Add(result); + MergeData(result, temp, new MetadataFields[] { }, false, false); + MergeNewData(temp.Item, id); refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload; } @@ -703,37 +895,6 @@ namespace MediaBrowser.Providers.Manager } } - var orderedResults = new List<MetadataResult<TItemType>>(); - var preferredLanguage = NormalizeLanguage(id.MetadataLanguage); - - // prioritize results with matching ResultLanguage - foreach (var result in results) - { - if (!result.QueriedById) - { - break; - } - - if (string.Equals(NormalizeLanguage(result.ResultLanguage), preferredLanguage, StringComparison.OrdinalIgnoreCase) && result.QueriedById) - { - orderedResults.Add(result); - } - } - - // add all other results - foreach (var result in results) - { - if (!orderedResults.Contains(result)) - { - orderedResults.Add(result); - } - } - - foreach (var result in results) - { - MergeData(result, temp, new MetadataFields[] { }, false, false); - } - return refreshResult; } @@ -775,7 +936,7 @@ namespace MediaBrowser.Providers.Manager } } - private bool HasChanged(IHasMetadata item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService) + private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService) { try { @@ -800,7 +961,6 @@ namespace MediaBrowser.Providers.Manager { public ItemUpdateType UpdateType { get; set; } public string ErrorMessage { get; set; } - public List<Guid> Providers { get; set; } public int Failures { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index cdef42771a..4c9312b83b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -25,6 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using Priority_Queue; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Subtitles; namespace MediaBrowser.Providers.Manager { @@ -67,22 +68,18 @@ namespace MediaBrowser.Providers.Manager private IExternalId[] _externalIds; private readonly Func<ILibraryManager> _libraryManagerFactory; - private readonly IMemoryStreamFactory _memoryStreamProvider; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted; public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted; public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress; + private ISubtitleManager _subtitleManager; + /// <summary> /// Initializes a new instance of the <see cref="ProviderManager" /> class. /// </summary> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="libraryMonitor">The directory watchers.</param> - /// <param name="logManager">The log manager.</param> - /// <param name="fileSystem">The file system.</param> - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json, IMemoryStreamFactory memoryStreamProvider) + public ProviderManager(IHttpClient httpClient, ISubtitleManager subtitleManager, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; @@ -92,7 +89,7 @@ namespace MediaBrowser.Providers.Manager _appPaths = appPaths; _libraryManagerFactory = libraryManagerFactory; _json = json; - _memoryStreamProvider = memoryStreamProvider; + _subtitleManager = subtitleManager; } /// <summary> @@ -116,7 +113,7 @@ namespace MediaBrowser.Providers.Manager }).ToArray(); } - public Task<ItemUpdateType> RefreshSingleItem(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken) + public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken) { IMetadataService service = null; var type = item.GetType(); @@ -151,7 +148,7 @@ namespace MediaBrowser.Providers.Manager return Task.FromResult(ItemUpdateType.None); } - public async Task SaveImage(IHasMetadata item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) + public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) { using (var response = await _httpClient.GetResponse(new HttpRequestOptions { @@ -161,16 +158,26 @@ namespace MediaBrowser.Providers.Manager }).ConfigureAwait(false)) { + // Workaround for tvheadend channel icons + // TODO: Isolate this hack into the tvh plugin + if (string.IsNullOrEmpty(response.ContentType)) + { + if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1) + { + response.ContentType = "image/png"; + } + } + await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false); } } - public Task SaveImage(IHasMetadata item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) + public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) { - return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger, _memoryStreamProvider).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken); + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken); } - public Task SaveImage(IHasMetadata item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) + public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(source)) { @@ -179,10 +186,10 @@ namespace MediaBrowser.Providers.Manager var fileStream = _fileSystem.GetFileStream(source, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); - return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger, _memoryStreamProvider).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } - public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasMetadata item, RemoteImageQuery query, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken) { var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders); @@ -217,7 +224,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="preferredLanguages">The preferred languages.</param> /// <param name="type">The type.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null) + private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null) { try { @@ -253,7 +260,7 @@ namespace MediaBrowser.Providers.Manager /// </summary> /// <param name="item">The item.</param> /// <returns>IEnumerable{IImageProvider}.</returns> - public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(IHasMetadata item) + public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item) { return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo { @@ -262,64 +269,70 @@ namespace MediaBrowser.Providers.Manager }); } - public IEnumerable<IImageProvider> GetImageProviders(IHasMetadata item, ImageRefreshOptions refreshOptions) + public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions) { - return GetImageProviders(item, GetMetadataOptions(item), refreshOptions, false); + return GetImageProviders(item, _libraryManagerFactory().GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false); } - private IEnumerable<IImageProvider> GetImageProviders(IHasMetadata item, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) + private IEnumerable<IImageProvider> GetImageProviders(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) { // Avoid implicitly captured closure var currentOptions = options; - return ImageProviders.Where(i => CanRefresh(i, item, options, refreshOptions, includeDisabled)) - .OrderBy(i => - { - // See if there's a user-defined order - if (!(i is ILocalImageProvider)) - { - var index = Array.IndexOf(currentOptions.ImageFetcherOrder, i.Name); + var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); + var typeFetcherOrder = typeOptions == null ? null : typeOptions.ImageFetcherOrder; - if (index != -1) + return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled)) + .OrderBy(i => + { + // See if there's a user-defined order + if (!(i is ILocalImageProvider)) { - return index; + var fetcherOrder = typeFetcherOrder ?? currentOptions.ImageFetcherOrder; + + var index = Array.IndexOf(fetcherOrder, i.Name); + + if (index != -1) + { + return index; + } } - } - // Not configured. Just return some high number to put it at the end. - return 100; - }) + // Not configured. Just return some high number to put it at the end. + return 100; + }) .ThenBy(GetOrder); } - public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item) - where T : IHasMetadata + public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions) + where T : BaseItem { - var options = GetMetadataOptions(item); + var globalMetadataOptions = GetMetadataOptions(item); - return GetMetadataProvidersInternal<T>(item, options, false, false, true); + return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false); } - private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata, bool checkIsOwnedItem) - where T : IHasMetadata + private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata) + where T : BaseItem { // Avoid implicitly captured closure - var currentOptions = options; + var currentOptions = globalMetadataOptions; return _metadataProviders.OfType<IMetadataProvider<T>>() - .Where(i => CanRefresh(i, item, currentOptions, includeDisabled, forceEnableInternetMetadata, checkIsOwnedItem)) - .OrderBy(i => GetConfiguredOrder(i, options)) + .Where(i => CanRefresh(i, item, libraryOptions, currentOptions, includeDisabled, forceEnableInternetMetadata)) + .OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions)) .ThenBy(GetDefaultOrder); } - private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasMetadata item, bool includeDisabled) + private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled) { var options = GetMetadataOptions(item); + var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item); - return GetImageProviders(item, options, new ImageRefreshOptions(new DirectoryService(_logger, _fileSystem)), includeDisabled).OfType<IRemoteImageProvider>(); + return GetImageProviders(item, libraryOptions, options, new ImageRefreshOptions(new DirectoryService(_logger, _fileSystem)), includeDisabled).OfType<IRemoteImageProvider>(); } - private bool CanRefresh(IMetadataProvider provider, IHasMetadata item, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata, bool checkIsOwnedItem) + private bool CanRefresh(IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata) { if (!includeDisabled) { @@ -331,12 +344,7 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteMetadataProvider) { - if (!forceEnableInternetMetadata && !item.IsInternetMetadataEnabled()) - { - return false; - } - - if (Array.IndexOf(options.DisabledMetadataFetchers, provider.Name) != -1) + if (!forceEnableInternetMetadata && !item.IsMetadataFetcherEnabled(libraryOptions, provider.Name)) { return false; } @@ -349,7 +357,7 @@ namespace MediaBrowser.Providers.Manager } // If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files - if (checkIsOwnedItem && item.IsOwnedItem) + if (!item.OwnerId.Equals(Guid.Empty)) { if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider) { @@ -360,14 +368,14 @@ namespace MediaBrowser.Providers.Manager return true; } - private bool CanRefresh(IImageProvider provider, IHasMetadata item, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) + private bool CanRefresh(IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) { if (!includeDisabled) { // If locked only allow local providers if (item.IsLocked && !(provider is ILocalImageProvider)) { - if (refreshOptions.ImageRefreshMode != ImageRefreshMode.FullRefresh) + if (refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh) { return false; } @@ -375,18 +383,10 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteImageProvider || provider is IDynamicImageProvider) { - if (Array.IndexOf(options.DisabledImageFetchers, provider.Name) != -1) + if (!item.IsImageFetcherEnabled(libraryOptions, provider.Name)) { return false; } - - if (provider is IRemoteImageProvider) - { - if (!refreshOptions.ForceEnableInternetMetadata && !item.IsInternetMetadataEnabled()) - { - return false; - } - } } } @@ -418,12 +418,14 @@ namespace MediaBrowser.Providers.Manager return hasOrder.Order; } - private int GetConfiguredOrder(IMetadataProvider provider, MetadataOptions options) + private int GetConfiguredOrder(BaseItem item, IMetadataProvider provider, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions) { // See if there's a user-defined order if (provider is ILocalMetadataProvider) { - var index = Array.IndexOf(options.LocalMetadataReaderOrder, provider.Name); + var configuredOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadataReaderOrder; + + var index = Array.IndexOf(configuredOrder, provider.Name); if (index != -1) { @@ -434,7 +436,12 @@ namespace MediaBrowser.Providers.Manager // See if there's a user-defined order if (provider is IRemoteMetadataProvider) { - var index = Array.IndexOf(options.MetadataFetcherOrder, provider.Name); + var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); + var typeFetcherOrder = typeOptions == null ? null : typeOptions.MetadataFetcherOrder; + + var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder; + + var index = Array.IndexOf(fetcherOrder, provider.Name); if (index != -1) { @@ -470,22 +477,13 @@ namespace MediaBrowser.Providers.Manager GetPluginSummary<Series>(), GetPluginSummary<Season>(), GetPluginSummary<Episode>(), - GetPluginSummary<Person>(), GetPluginSummary<MusicAlbum>(), GetPluginSummary<MusicArtist>(), GetPluginSummary<Audio>(), GetPluginSummary<AudioBook>(), - GetPluginSummary<AudioPodcast>(), - GetPluginSummary<Genre>(), GetPluginSummary<Studio>(), - GetPluginSummary<GameGenre>(), - GetPluginSummary<MusicGenre>(), GetPluginSummary<MusicVideo>(), - GetPluginSummary<Video>(), - GetPluginSummary<LiveTvChannel>(), - GetPluginSummary<LiveTvProgram>(), - GetPluginSummary<LiveTvVideoRecording>(), - GetPluginSummary<LiveTvAudioRecording>() + GetPluginSummary<Video>() }; } @@ -506,13 +504,24 @@ namespace MediaBrowser.Providers.Manager ItemType = typeof(T).Name }; - var imageProviders = GetImageProviders(dummy, options, new ImageRefreshOptions(new DirectoryService(_logger, _fileSystem)), true).ToList(); + var libraryOptions = new LibraryOptions(); + + var imageProviders = GetImageProviders(dummy, libraryOptions, options, new ImageRefreshOptions(new DirectoryService(_logger, _fileSystem)), true).ToList(); var pluginList = summary.Plugins.ToList(); - AddMetadataPlugins(pluginList, dummy, options); + AddMetadataPlugins(pluginList, dummy, libraryOptions, options); AddImagePlugins(pluginList, dummy, imageProviders); + var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy); + + // Subtitle fetchers + pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin + { + Name = i.Name, + Type = MetadataPluginType.SubtitleFetcher + })); + summary.Plugins = pluginList.ToArray(pluginList.Count); var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>() @@ -527,10 +536,10 @@ namespace MediaBrowser.Providers.Manager return summary; } - private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, MetadataOptions options) - where T : IHasMetadata + private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOptions options) + where T : BaseItem { - var providers = GetMetadataProvidersInternal<T>(item, options, true, false, false).ToList(); + var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList(); // Locals list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin @@ -547,7 +556,7 @@ namespace MediaBrowser.Providers.Manager })); // Savers - list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, ItemUpdateType.MetadataEdit, true)).OrderBy(i => i.Name).Select(i => new MetadataPlugin + list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, true)).OrderBy(i => i.Name).Select(i => new MetadataPlugin { Name = i.Name, Type = MetadataPluginType.MetadataSaver @@ -555,7 +564,7 @@ namespace MediaBrowser.Providers.Manager } private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders) - where T : IHasMetadata + where T : BaseItem { // Locals @@ -565,17 +574,15 @@ namespace MediaBrowser.Providers.Manager Type = MetadataPluginType.LocalImageProvider })); - var enableInternet = item.IsInternetMetadataEnabled(); - // Fetchers - list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (enableInternet && i is IRemoteImageProvider)).Select(i => new MetadataPlugin + list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i => new MetadataPlugin { Name = i.Name, Type = MetadataPluginType.ImageFetcher })); } - public MetadataOptions GetMetadataOptions(IHasMetadata item) + public MetadataOptions GetMetadataOptions(BaseItem item) { var type = item.GetType().Name; @@ -587,7 +594,7 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Saves the metadata. /// </summary> - public void SaveMetadata(IHasMetadata item, ItemUpdateType updateType) + public void SaveMetadata(BaseItem item, ItemUpdateType updateType) { SaveMetadata(item, updateType, _savers); } @@ -595,7 +602,7 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Saves the metadata. /// </summary> - public void SaveMetadata(IHasMetadata item, ItemUpdateType updateType, IEnumerable<string> savers) + public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers) { SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase))); } @@ -607,9 +614,11 @@ namespace MediaBrowser.Providers.Manager /// <param name="updateType">Type of the update.</param> /// <param name="savers">The savers.</param> /// <returns>Task.</returns> - private void SaveMetadata(IHasMetadata item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers) + private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers) { - foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, updateType, false))) + var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item); + + foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false))) { _logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name); @@ -660,49 +669,57 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Determines whether [is saver enabled for item] [the specified saver]. /// </summary> - /// <param name="saver">The saver.</param> - /// <param name="item">The item.</param> - /// <param name="updateType">Type of the update.</param> - /// <param name="includeDisabled">if set to <c>true</c> [include disabled].</param> - /// <returns><c>true</c> if [is saver enabled for item] [the specified saver]; otherwise, <c>false</c>.</returns> - private bool IsSaverEnabledForItem(IMetadataSaver saver, IHasMetadata item, ItemUpdateType updateType, bool includeDisabled) + private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdateType updateType, bool includeDisabled) { var options = GetMetadataOptions(item); try { - var isEnabledFor = saver.IsEnabledFor(item, updateType); + if (!saver.IsEnabledFor(item, updateType)) + { + return false; + } if (!includeDisabled) { - if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) + if (libraryOptions.MetadataSavers == null) { - return false; - } + if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) + { + return false; + } - if (!item.IsSaveLocalMetadataEnabled()) - { - if (updateType >= ItemUpdateType.MetadataEdit) + if (!item.IsSaveLocalMetadataEnabled()) { - var fileSaver = saver as IMetadataFileSaver; + if (updateType >= ItemUpdateType.MetadataEdit) + { + var fileSaver = saver as IMetadataFileSaver; - // Manual edit occurred - // Even if save local is off, save locally anyway if the metadata file already exists - if (fileSaver == null || !isEnabledFor || !_fileSystem.FileExists(fileSaver.GetSavePath(item))) + // Manual edit occurred + // Even if save local is off, save locally anyway if the metadata file already exists + if (fileSaver == null || !_fileSystem.FileExists(fileSaver.GetSavePath(item))) + { + return false; + } + } + else { + // Manual edit did not occur + // Since local metadata saving is disabled, consider it disabled return false; } } - else + } + else + { + if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) { - // Manual edit did not occur - // Since local metadata saving is disabled, consider it disabled return false; } } } - return isEnabledFor; + return true; } catch (Exception ex) { @@ -711,23 +728,48 @@ namespace MediaBrowser.Providers.Manager } } - public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, - CancellationToken cancellationToken) + public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken) where TItemType : BaseItem, new() where TLookupType : ItemLookupInfo { - // Give it a dummy path just so that it looks like a file system item - var dummy = new TItemType + BaseItem referenceItem = null; + + if (!searchInfo.ItemId.Equals(Guid.Empty)) { - Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"), - ParentId = Guid.NewGuid() - }; + referenceItem = _libraryManagerFactory().GetItemById(searchInfo.ItemId); + } - dummy.SetParent(new Folder()); + return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken); + } - var options = GetMetadataOptions(dummy); + public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken) + where TItemType : BaseItem, new() + where TLookupType : ItemLookupInfo + { + LibraryOptions libraryOptions; - var providers = GetMetadataProvidersInternal<TItemType>(dummy, options, searchInfo.IncludeDisabledProviders, false, false) + if (referenceItem == null) + { + // Give it a dummy path just so that it looks like a file system item + var dummy = new TItemType + { + Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"), + ParentId = Guid.NewGuid() + }; + + dummy.SetParent(new Folder()); + + referenceItem = dummy; + libraryOptions = new LibraryOptions(); + } + else + { + libraryOptions = _libraryManagerFactory().GetLibraryOptions(referenceItem); + } + + var options = GetMetadataOptions(referenceItem); + + var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.IncludeDisabledProviders, false) .OfType<IRemoteSearchProvider<TLookupType>>(); if (!string.IsNullOrEmpty(searchInfo.SearchProviderName)) @@ -1006,15 +1048,6 @@ namespace MediaBrowser.Providers.Manager // Try to throttle this a little bit. await Task.Delay(100).ConfigureAwait(false); - if (refreshItem.Item2.ValidateChildren) - { - var folder = item as Folder; - if (folder != null) - { - await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); - } - } - var artist = item as MusicArtist; var task = artist == null ? RefreshItem(item, refreshItem.Item2, cancellationToken) @@ -1082,7 +1115,7 @@ namespace MediaBrowser.Providers.Manager .GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, - ArtistIds = new[] { item.Id.ToString("N") }, + ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { EnableImages = false @@ -1110,10 +1143,10 @@ namespace MediaBrowser.Providers.Manager } } - public Task RefreshFullItem(IHasMetadata item, MetadataRefreshOptions options, + public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return RefreshItem((BaseItem)item, options, cancellationToken); + return RefreshItem(item, options, cancellationToken); } private bool _disposed; @@ -1125,7 +1158,6 @@ namespace MediaBrowser.Providers.Manager { _disposeCancellationTokenSource.Cancel(); } - GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index b727e13546..3a961fe0ec 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -63,21 +63,12 @@ namespace MediaBrowser.Providers.Manager if (!lockedFields.Contains(MetadataFields.Genres)) { - if (replaceData || target.Genres.Count == 0) + if (replaceData || target.Genres.Length == 0) { target.Genres = source.Genres; } } - if (replaceData || string.IsNullOrEmpty(target.HomePageUrl)) - { - target.HomePageUrl = source.HomePageUrl; - if (!string.IsNullOrWhiteSpace(target.HomePageUrl) && target.HomePageUrl.IndexOf("http", StringComparison.OrdinalIgnoreCase) != 0) - { - target.HomePageUrl = "http://" + target.HomePageUrl; - } - } - if (replaceData || !target.IndexNumber.HasValue) { target.IndexNumber = source.IndexNumber; @@ -189,22 +180,29 @@ namespace MediaBrowser.Providers.Manager MergeVideoInfo(source, target, lockedFields, replaceData); MergeDisplayOrder(source, target, lockedFields, replaceData); - //if (!lockedFields.Contains(MetadataFields.SortName)) + if (replaceData || string.IsNullOrEmpty(target.ForcedSortName)) { - if (replaceData || string.IsNullOrEmpty(target.ForcedSortName)) - { - var forcedSortName = source.ForcedSortName; + var forcedSortName = source.ForcedSortName; - if (!string.IsNullOrWhiteSpace(forcedSortName)) - { - target.ForcedSortName = forcedSortName; - } + if (!string.IsNullOrWhiteSpace(forcedSortName)) + { + target.ForcedSortName = forcedSortName; } } if (mergeMetadataSettings) { - MergeMetadataSettings(source, target); + target.LockedFields = source.LockedFields; + target.IsLocked = source.IsLocked; + + // Grab the value if it's there, but if not then don't overwrite the default + if (source.DateCreated != default(DateTime)) + { + target.DateCreated = source.DateCreated; + } + + target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode; + target.PreferredMetadataLanguage = source.PreferredMetadataLanguage; } } @@ -233,22 +231,6 @@ namespace MediaBrowser.Providers.Manager } } - public static void MergeMetadataSettings(BaseItem source, - BaseItem target) - { - target.LockedFields = source.LockedFields; - target.IsLocked = source.IsLocked; - - // Grab the value if it's there, but if not then don't overwrite the default - if (source.DateCreated != default(DateTime)) - { - target.DateCreated = source.DateCreated; - } - - target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode; - target.PreferredMetadataLanguage = source.PreferredMetadataLanguage; - } - private static void MergeDisplayOrder(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { var sourceHasDisplayOrder = source as IHasDisplayOrder; @@ -256,7 +238,15 @@ namespace MediaBrowser.Providers.Manager if (sourceHasDisplayOrder != null && targetHasDisplayOrder != null) { - targetHasDisplayOrder.DisplayOrder = sourceHasDisplayOrder.DisplayOrder; + if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder)) + { + var displayOrder = sourceHasDisplayOrder.DisplayOrder; + + if (!string.IsNullOrWhiteSpace(displayOrder)) + { + targetHasDisplayOrder.DisplayOrder = displayOrder; + } + } } } @@ -284,15 +274,9 @@ namespace MediaBrowser.Providers.Manager private static void MergeTrailers(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { - var sourceCast = source as IHasTrailers; - var targetCast = target as IHasTrailers; - - if (sourceCast != null && targetCast != null) + if (replaceData || target.RemoteTrailers.Length == 0) { - if (replaceData || targetCast.RemoteTrailers.Length == 0) - { - targetCast.RemoteTrailers = sourceCast.RemoteTrailers; - } + target.RemoteTrailers = source.RemoteTrailers; } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index ae44993507..480d6c678c 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -1,172 +1,22 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>MediaBrowser.Providers</RootNamespace> - <AssemblyName>MediaBrowser.Providers</AssemblyName> - <FileAlignment>512</FileAlignment> - <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> - <ProductVersion>10.0.0</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> - <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>none</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> +<Project Sdk="Microsoft.NET.Sdk"> + <ItemGroup> - <Compile Include="..\SharedVersion.cs"> - <Link>Properties\SharedVersion.cs</Link> - </Compile> - <Compile Include="Books\AudioBookMetadataService.cs" /> - <Compile Include="Books\AudioPodcastMetadataService.cs" /> - <Compile Include="Books\BookMetadataService.cs" /> - <Compile Include="Books\GoogleBooksProvider.cs" /> - <Compile Include="BoxSets\BoxSetMetadataService.cs" /> - <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" /> - <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> - <Compile Include="Channels\ChannelMetadataService.cs" /> - <Compile Include="Chapters\ChapterManager.cs" /> - <Compile Include="Folders\CollectionFolderMetadataService.cs" /> - <Compile Include="Folders\FolderMetadataService.cs" /> - <Compile Include="Folders\UserViewMetadataService.cs" /> - <Compile Include="GameGenres\GameGenreMetadataService.cs" /> - <Compile Include="Games\GameMetadataService.cs" /> - <Compile Include="Games\GameSystemMetadataService.cs" /> - <Compile Include="Genres\GenreMetadataService.cs" /> - <Compile Include="LiveTv\AudioRecordingService.cs" /> - <Compile Include="LiveTv\ChannelMetadataService.cs" /> - <Compile Include="LiveTv\ProgramMetadataService.cs" /> - <Compile Include="LiveTv\VideoRecordingService.cs" /> - <Compile Include="Manager\GenericPriorityQueue.cs" /> - <Compile Include="Manager\GenericPriorityQueueNode.cs" /> - <Compile Include="Manager\IFixedSizePriorityQueue.cs" /> - <Compile Include="Manager\ImageSaver.cs" /> - <Compile Include="Manager\IPriorityQueue.cs" /> - <Compile Include="Manager\ItemImageProvider.cs" /> - <Compile Include="Manager\ProviderManager.cs" /> - <Compile Include="Manager\MetadataService.cs" /> - <Compile Include="Manager\SimplePriorityQueue.cs" /> - <Compile Include="MediaInfo\FFProbeAudioInfo.cs" /> - <Compile Include="MediaInfo\FFProbeProvider.cs" /> - <Compile Include="MediaInfo\FFProbeVideoInfo.cs" /> - <Compile Include="MediaInfo\SubtitleDownloader.cs" /> - <Compile Include="MediaInfo\SubtitleResolver.cs" /> - <Compile Include="MediaInfo\SubtitleScheduledTask.cs" /> - <Compile Include="Movies\MovieDbTrailerProvider.cs" /> - <Compile Include="Movies\MovieExternalIds.cs" /> - <Compile Include="Movies\GenericMovieDbInfo.cs" /> - <Compile Include="Movies\MovieDbSearch.cs" /> - <Compile Include="Movies\MovieMetadataService.cs" /> - <Compile Include="Movies\TmdbSettings.cs" /> - <Compile Include="ImagesByName\ImageUtils.cs" /> - <Compile Include="MediaInfo\AudioImageProvider.cs" /> - <Compile Include="MediaInfo\VideoImageProvider.cs" /> - <Compile Include="Movies\MovieDbImageProvider.cs" /> - <Compile Include="Movies\FanartMovieImageProvider.cs" /> - <Compile Include="MusicGenres\MusicGenreMetadataService.cs" /> - <Compile Include="Music\AlbumMetadataService.cs" /> - <Compile Include="Music\ArtistMetadataService.cs" /> - <Compile Include="Music\AudioDbAlbumImageProvider.cs" /> - <Compile Include="Music\AudioDbAlbumProvider.cs" /> - <Compile Include="Music\AudioDbArtistImageProvider.cs" /> - <Compile Include="Music\AudioDbArtistProvider.cs" /> - <Compile Include="Music\AudioDbExternalIds.cs" /> - <Compile Include="Music\AudioMetadataService.cs" /> - <Compile Include="Music\Extensions.cs" /> - <Compile Include="Music\MovieDbMusicVideoProvider.cs" /> - <Compile Include="Music\MusicBrainzArtistProvider.cs" /> - <Compile Include="Music\MusicExternalIds.cs" /> - <Compile Include="Music\MusicVideoMetadataService.cs" /> - <Compile Include="Omdb\OmdbImageProvider.cs" /> - <Compile Include="Omdb\OmdbProvider.cs" /> - <Compile Include="Omdb\OmdbItemProvider.cs" /> - <Compile Include="People\MovieDbPersonImageProvider.cs" /> - <Compile Include="Movies\MovieDbProvider.cs" /> - <Compile Include="Music\FanArtAlbumProvider.cs" /> - <Compile Include="Music\FanArtArtistProvider.cs" /> - <Compile Include="Music\MusicBrainzAlbumProvider.cs" /> - <Compile Include="People\PersonMetadataService.cs" /> - <Compile Include="People\MovieDbPersonProvider.cs" /> - <Compile Include="Photos\PhotoAlbumMetadataService.cs" /> - <Compile Include="Photos\PhotoMetadataService.cs" /> - <Compile Include="Playlists\PlaylistMetadataService.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Manager\ProviderUtils.cs" /> - <Compile Include="Studios\StudiosImageProvider.cs" /> - <Compile Include="Studios\StudioMetadataService.cs" /> - <Compile Include="Subtitles\SubtitleManager.cs" /> - <Compile Include="TV\DummySeasonProvider.cs" /> - <Compile Include="TV\EpisodeMetadataService.cs" /> - <Compile Include="TV\FanArt\FanArtSeasonProvider.cs" /> - <Compile Include="TV\FanArt\FanartSeriesProvider.cs" /> - <Compile Include="TV\MissingEpisodeProvider.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbProviderBase.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbEpisodeImageProvider.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbSeasonProvider.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbSeriesImageProvider.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbSeriesProvider.cs" /> - <Compile Include="TV\TheMovieDb\MovieDbEpisodeProvider.cs" /> - <Compile Include="TV\Omdb\OmdbEpisodeProvider.cs" /> - <Compile Include="TV\SeriesMetadataService.cs" /> - <Compile Include="TV\TheTVDB\TvdbEpisodeImageProvider.cs" /> - <Compile Include="People\TvdbPersonImageProvider.cs" /> - <Compile Include="TV\TheTVDB\TvdbSeasonImageProvider.cs" /> - <Compile Include="TV\TheTVDB\TvdbSeriesImageProvider.cs" /> - <Compile Include="TV\SeasonMetadataService.cs" /> - <Compile Include="TV\TheTVDB\TvdbEpisodeProvider.cs" /> - <Compile Include="TV\TheTVDB\TvdbSeriesProvider.cs" /> - <Compile Include="TV\TheTVDB\TvdbPrescanTask.cs" /> - <Compile Include="TV\TvExternalIds.cs" /> - <Compile Include="Users\UserMetadataService.cs" /> - <Compile Include="Videos\VideoMetadataService.cs" /> - <Compile Include="Years\YearMetadataService.cs" /> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> + <ProjectReference Include="..\DvdLib\DvdLib.csproj" /> </ItemGroup> + <ItemGroup> - <ProjectReference Include="..\DvdLib\DvdLib.csproj"> - <Project>{713f42b5-878e-499d-a878-e4c652b1d5e8}</Project> - <Name>DvdLib</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> - <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project> - <Name>MediaBrowser.Common</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project> - <Name>MediaBrowser.Controller</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project> - <Name>MediaBrowser.Model</Name> - </ProjectReference> + <Compile Include="..\SharedVersion.cs" /> </ItemGroup> - <ItemGroup /> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project>
\ No newline at end of file + + <ItemGroup> + <PackageReference Include="PlaylistsNET" Version="1.0.2" /> + </ItemGroup> + + <PropertyGroup> + <TargetFramework>netcoreapp2.1</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + </PropertyGroup> + +</Project> diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.nuget.targets b/MediaBrowser.Providers/MediaBrowser.Providers.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/MediaBrowser.Providers/MediaBrowser.Providers.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no"?> -<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Target Name="EmitMSBuildWarning" BeforeTargets="Build"> - <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." /> - </Target> -</Project>
\ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 1a650082c2..2c0d5bcbc2 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -32,12 +32,12 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { ImageType.Primary }; } - public Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken) + public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var audio = (Audio)item; @@ -92,27 +92,22 @@ namespace MediaBrowser.Providers.MediaInfo private string GetAudioImagePath(Audio item) { - string filename; + string filename = null; if (item.GetType() == typeof(Audio)) { - filename = item.Album ?? string.Empty; - filename += string.Join(",", item.Artists); + var albumArtist = item.AlbumArtists.FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(item.Album)) + if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist)) { - filename += "_" + item.Album; - } - else if (!string.IsNullOrWhiteSpace(item.Name)) - { - filename += "_" + item.Name; + filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N"); } else { - filename += "_" + item.Id.ToString("N"); + filename = item.Id.ToString("N"); } - filename = filename.GetMD5() + ".jpg"; + filename += ".jpg"; } else { @@ -138,11 +133,20 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Image Extractor"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { + if (item.IsShortcut) + { + return false; + } + if (!item.IsFileProtocol) + { + return false; + } + var audio = item as Audio; - return item.LocationType == LocationType.FileSystem && audio != null; + return audio != null; } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index cb0075b335..b3fc2a9d74 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -10,10 +10,12 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Threading; using System.Threading.Tasks; using System.Linq; +using MediaBrowser.Model.Dto; +using System; +using MediaBrowser.Controller.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -24,65 +26,52 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IApplicationPaths _appPaths; private readonly IJsonSerializer _json; private readonly ILibraryManager _libraryManager; + private readonly IMediaSourceManager _mediaSourceManager; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo, IApplicationPaths appPaths, IJsonSerializer json, ILibraryManager libraryManager) + public FFProbeAudioInfo(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IApplicationPaths appPaths, IJsonSerializer json, ILibraryManager libraryManager) { _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; _appPaths = appPaths; _json = json; _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; } - public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken) + public async Task<ItemUpdateType> Probe<T>(T item, MetadataRefreshOptions options, + CancellationToken cancellationToken) where T : Audio { - var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); + var path = item.Path; + var protocol = item.PathProtocol ?? MediaProtocol.File; - cancellationToken.ThrowIfCancellationRequested(); - - Fetch(item, cancellationToken, result); - - return ItemUpdateType.MetadataImport; - } + if (!item.IsShortcut || options.EnableRemoteContentProbe) + { + if (item.IsShortcut) + { + path = item.ShortcutPath; + protocol = _mediaSourceManager.GetPathProtocol(path); + } - private const string SchemaVersion = "3"; + var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + { + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = protocol + } - private async Task<Model.MediaInfo.MediaInfo> GetMediaInfo(BaseItem item, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - //var idString = item.Id.ToString("N"); - //var cachePath = Path.Combine(_appPaths.CachePath, - // "ffprobe-audio", - // idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); - - //try - //{ - // return _json.DeserializeFromFile<Model.MediaInfo.MediaInfo>(cachePath); - //} - //catch (FileNotFoundException) - //{ - - //} - //catch (DirectoryNotFoundException) - //{ - //} - - var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - InputPath = item.Path, - MediaType = DlnaProfileType.Audio, - Protocol = MediaProtocol.File + }, cancellationToken).ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - //Directory.CreateDirectory(_fileSystem.GetDirectoryName(cachePath)); - //_json.SerializeToFile(result, cachePath); + Fetch(item, cancellationToken, result); + } - return result; + return ItemUpdateType.MetadataImport; } /// <summary> @@ -156,7 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!audio.LockedFields.Contains(MetadataFields.Genres)) { - audio.Genres.Clear(); + audio.Genres = Array.Empty<string>(); foreach (var genre in data.Genres) { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 6a2677b433..f7cbf72116 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -21,22 +21,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Channels; namespace MediaBrowser.Providers.MediaInfo { public class FFProbeProvider : ICustomMetadataProvider<Episode>, ICustomMetadataProvider<MusicVideo>, ICustomMetadataProvider<Movie>, - ICustomMetadataProvider<LiveTvVideoRecording>, - ICustomMetadataProvider<LiveTvAudioRecording>, ICustomMetadataProvider<Trailer>, ICustomMetadataProvider<Video>, ICustomMetadataProvider<Audio>, - ICustomMetadataProvider<AudioPodcast>, ICustomMetadataProvider<AudioBook>, IHasOrder, IForcedProvider, - IPreRefreshProvider + IPreRefreshProvider, + IHasItemChangeMonitor { private readonly ILogger _logger; private readonly IIsoManager _isoManager; @@ -52,28 +51,59 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly IChannelManager _channelManager; + private readonly IMediaSourceManager _mediaSourceManager; public string Name { get { return "ffprobe"; } } - public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellationToken) + public bool HasChanged(BaseItem item, IDirectoryService directoryService) { - return FetchVideoInfo(item, options, cancellationToken); + var video = item as Video; + if (video == null || video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Iso) + { + var path = item.Path; + + if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol) + { + var file = directoryService.GetFile(path); + if (file != null && file.LastWriteTimeUtc != item.DateModified) + { + _logger.Debug("Refreshing {0} due to date modified timestamp change.", path); + return true; + } + } + } + + if (item.SupportsLocalMetadata) + { + if (video != null && !video.IsPlaceHolder) + { + if (!video.SubtitleFiles + .SequenceEqual(_subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) + { + _logger.Debug("Refreshing {0} due to external subtitles change.", item.Path); + return true; + } + } + } + + return false; } - public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return FetchVideoInfo(item, options, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return FetchVideoInfo(item, options, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, MetadataRefreshOptions options, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return FetchVideoInfo(item, options, cancellationToken); } @@ -90,25 +120,16 @@ namespace MediaBrowser.Providers.MediaInfo public Task<ItemUpdateType> FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchAudioInfo(item, cancellationToken); - } - - public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, MetadataRefreshOptions options, CancellationToken cancellationToken) - { - return FetchAudioInfo(item, cancellationToken); - } - - public Task<ItemUpdateType> FetchAsync(AudioPodcast item, MetadataRefreshOptions options, CancellationToken cancellationToken) - { - return FetchAudioInfo(item, cancellationToken); + return FetchAudioInfo(item, options, cancellationToken); } public Task<ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchAudioInfo(item, cancellationToken); + return FetchAudioInfo(item, options, cancellationToken); } - public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager) + private SubtitleResolver _subtitleResolver; + public FFProbeProvider(ILogger logger, IMediaSourceManager mediaSourceManager, IChannelManager channelManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager) { _logger = logger; _isoManager = isoManager; @@ -124,28 +145,37 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _channelManager = channelManager; + _mediaSourceManager = mediaSourceManager; + + _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem); } private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None); public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Video { - if (item.LocationType != LocationType.FileSystem) + if (item.VideoType == VideoType.Iso) { return _cachedTask; } - if (item.VideoType == VideoType.Iso && !_isoManager.CanMount(item.Path)) + if (item.IsPlaceHolder) { return _cachedTask; } - if (item.IsPlaceHolder) + if (!item.IsCompleteMedia) { return _cachedTask; } - if (!item.IsCompleteMedia) + if (item.IsVirtualItem) + { + return _cachedTask; + } + + if (!options.EnableRemoteContentProbe && !item.IsFileProtocol) { return _cachedTask; } @@ -155,31 +185,47 @@ namespace MediaBrowser.Providers.MediaInfo FetchShortcutInfo(item); } - var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager); + var prober = new FFProbeVideoInfo(_logger, _mediaSourceManager, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager); return prober.ProbeVideo(item, options, cancellationToken); } - private void FetchShortcutInfo(Video video) + private string NormalizeStrmLine(string line) { - video.ShortcutPath = _fileSystem.ReadAllText(video.Path) - .Replace("\t", string.Empty) + return line.Replace("\t", string.Empty) .Replace("\r", string.Empty) .Replace("\n", string.Empty) .Trim(); } - public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken) + private void FetchShortcutInfo(BaseItem item) + { + item.ShortcutPath = _fileSystem.ReadAllLines(item.Path) + .Select(NormalizeStrmLine) + .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith("#", StringComparison.OrdinalIgnoreCase)); + } + + public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Audio { - if (item.LocationType != LocationType.FileSystem) + if (item.IsVirtualItem) + { + return _cachedTask; + } + + if (!options.EnableRemoteContentProbe && !item.IsFileProtocol) { return _cachedTask; } - var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo, _appPaths, _json, _libraryManager); + if (item.IsShortcut) + { + FetchShortcutInfo(item); + } + + var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _appPaths, _json, _libraryManager); - return prober.Probe(item, cancellationToken); + return prober.Probe(item, options, cancellationToken); } public int Order diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 443eb6eda4..c7fc086be2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -26,6 +26,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Providers.MediaInfo { @@ -45,8 +46,9 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly IMediaSourceManager _mediaSourceManager; - public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager) + public FFProbeVideoInfo(ILogger logger, IMediaSourceManager mediaSourceManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager) { _logger = logger; _isoManager = isoManager; @@ -62,6 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; } public async Task<ItemUpdateType> ProbeVideo<T>(T item, @@ -69,93 +72,81 @@ namespace MediaBrowser.Providers.MediaInfo CancellationToken cancellationToken) where T : Video { - var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); - BlurayDiscInfo blurayDiscInfo = null; - try + Model.MediaInfo.MediaInfo mediaInfoResult = null; + + if (!item.IsShortcut || options.EnableRemoteContentProbe) { - Model.MediaInfo.MediaInfo mediaInfoResult = null; + string[] streamFileNames = null; - if (!item.IsShortcut) + if (item.VideoType == VideoType.Dvd) { - string[] streamFileNames = null; - - if (item.VideoType == VideoType.Iso) - { - item.IsoType = DetermineIsoType(isoMount); - } + streamFileNames = FetchFromDvdLib(item); - if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd)) + if (streamFileNames.Length == 0) { - streamFileNames = FetchFromDvdLib(item, isoMount); - - if (streamFileNames.Length == 0) - { - _logger.Error("No playable vobs found in dvd structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } + _logger.Error("No playable vobs found in dvd structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; } + } - else if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType == IsoType.BluRay)) - { - var inputPath = isoMount != null ? isoMount.MountedPath : item.Path; + else if (item.VideoType == VideoType.BluRay) + { + var inputPath = item.Path; - blurayDiscInfo = GetBDInfo(inputPath); + blurayDiscInfo = GetBDInfo(inputPath); - streamFileNames = blurayDiscInfo.Files; + streamFileNames = blurayDiscInfo.Files; - if (streamFileNames.Length == 0) - { - _logger.Error("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } - } - - if (streamFileNames == null) + if (streamFileNames.Length == 0) { - streamFileNames = new string[] { }; + _logger.Error("No playable vobs found in bluray structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; } - - mediaInfoResult = await GetMediaInfo(item, isoMount, streamFileNames, cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); } - await Fetch(item, cancellationToken, mediaInfoResult, isoMount, blurayDiscInfo, options).ConfigureAwait(false); - - } - finally - { - if (isoMount != null) + if (streamFileNames == null) { - isoMount.Dispose(); + streamFileNames = Array.Empty<string>(); } + + mediaInfoResult = await GetMediaInfo(item, streamFileNames, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); } + await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false); + return ItemUpdateType.MetadataImport; } private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(Video item, - IIsoMount isoMount, string[] streamFileNames, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var protocol = item.LocationType == LocationType.Remote - ? MediaProtocol.Http - : MediaProtocol.File; + var path = item.Path; + var protocol = item.PathProtocol ?? MediaProtocol.File; + + if (item.IsShortcut) + { + path = item.ShortcutPath; + protocol = _mediaSourceManager.GetPathProtocol(path); + } return _mediaEncoder.GetMediaInfo(new MediaInfoRequest { PlayableStreamFileNames = streamFileNames, - MountedIso = isoMount, ExtractChapters = true, - VideoType = item.VideoType, MediaType = DlnaProfileType.Video, - InputPath = item.Path, - Protocol = protocol + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = protocol, + VideoType = item.VideoType + } }, cancellationToken); } @@ -163,7 +154,6 @@ namespace MediaBrowser.Providers.MediaInfo protected async Task Fetch(Video video, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo, - IIsoMount isoMount, BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) { @@ -222,10 +212,11 @@ namespace MediaBrowser.Providers.MediaInfo video.Video3DFormat = video.Video3DFormat ?? mediaInfo.Video3DFormat; } - video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1260); - var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + video.Height = videoStream == null ? 0 : videoStream.Height ?? 0; + video.Width = videoStream == null ? 0 : videoStream.Width ?? 0; + video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); @@ -370,9 +361,9 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Genres)) { - if (video.Genres.Count == 0 || isFullRefresh) + if (video.Genres.Length == 0 || isFullRefresh) { - video.Genres.Clear(); + video.Genres = Array.Empty<string>(); foreach (var genre in data.Genres) { @@ -423,7 +414,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles) { // Don't use the embedded name for extras because it will often be the same name as the movie - if (!video.ExtraType.HasValue && !video.IsOwnedItem) + if (!video.ExtraType.HasValue) { video.Name = data.Name; } @@ -498,19 +489,46 @@ namespace MediaBrowser.Providers.MediaInfo var subtitleOptions = GetOptions(); - if (enableSubtitleDownloading && (subtitleOptions.DownloadEpisodeSubtitles && + var libraryOptions = _libraryManager.GetLibraryOptions(video); + + string[] subtitleDownloadLanguages; + bool SkipIfEmbeddedSubtitlesPresent; + bool SkipIfAudioTrackMatches; + bool RequirePerfectMatch; + bool enabled; + + if (libraryOptions.SubtitleDownloadLanguages == null) + { + subtitleDownloadLanguages = subtitleOptions.DownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches; + RequirePerfectMatch = subtitleOptions.RequirePerfectMatch; + enabled = (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || (subtitleOptions.DownloadMovieSubtitles && - video is Movie)) + video is Movie); + } + else + { + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + enabled = true; + } + + if (enableSubtitleDownloading && enabled) { var downloadedLanguages = await new SubtitleDownloader(_logger, _subtitleManager) .DownloadSubtitles(video, currentStreams.Concat(externalSubtitleStreams).ToList(), - subtitleOptions.SkipIfEmbeddedSubtitlesPresent, - subtitleOptions.SkipIfAudioTrackMatches, - subtitleOptions.RequirePerfectMatch, - subtitleOptions.DownloadLanguages, + SkipIfEmbeddedSubtitlesPresent, + SkipIfAudioTrackMatches, + RequirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, cancellationToken).ConfigureAwait(false); // Rescan @@ -520,7 +538,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).OrderBy(i => i).ToArray(); + video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).ToArray(); currentStreams.AddRange(externalSubtitleStreams); } @@ -565,9 +583,9 @@ namespace MediaBrowser.Providers.MediaInfo } } - private string[] FetchFromDvdLib(Video item, IIsoMount mount) + private string[] FetchFromDvdLib(Video item) { - var path = mount == null ? item.Path : mount.MountedPath; + var path = item.Path; var dvd = new Dvd(path, _fileSystem); var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault(); @@ -580,7 +598,7 @@ namespace MediaBrowser.Providers.MediaInfo item.RunTimeTicks = GetRuntime(primaryTitle); } - return _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, mount, titleNumber) + return _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null, titleNumber) .Select(Path.GetFileName) .ToArray(); } @@ -592,43 +610,5 @@ namespace MediaBrowser.Providers.MediaInfo .Select(i => i.Ticks) .Sum(); } - - /// <summary> - /// Mounts the iso if needed. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>IsoMount.</returns> - protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken) - { - if (item.VideoType == VideoType.Iso) - { - return _isoManager.Mount(item.Path, cancellationToken); - } - - return Task.FromResult<IIsoMount>(null); - } - - /// <summary> - /// Determines the type of the iso. - /// </summary> - /// <param name="isoMount">The iso mount.</param> - /// <returns>System.Nullable{IsoType}.</returns> - private IsoType? DetermineIsoType(IIsoMount isoMount) - { - var fileSystemEntries = _fileSystem.GetFileSystemEntryPaths(isoMount.MountedPath).Select(Path.GetFileName).ToList(); - - if (fileSystemEntries.Contains("video_ts", StringComparer.OrdinalIgnoreCase) || - fileSystemEntries.Contains("VIDEO_TS.IFO", StringComparer.OrdinalIgnoreCase)) - { - return IsoType.Dvd; - } - if (fileSystemEntries.Contains("bdmv", StringComparer.OrdinalIgnoreCase)) - { - return IsoType.BluRay; - } - - return null; - } } }
\ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 7c6b55d3ac..6b396967ec 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -31,6 +31,8 @@ namespace MediaBrowser.Providers.MediaInfo bool skipIfAudioTrackMatches, bool requirePerfectMatch, IEnumerable<string> languages, + string[] disabledSubtitleFetchers, + string[] subtitleFetcherOrder, CancellationToken cancellationToken) { var downloadedLanguages = new List<string>(); @@ -38,7 +40,7 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var lang in languages) { var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, requirePerfectMatch, lang, cancellationToken).ConfigureAwait(false); + skipIfAudioTrackMatches, requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, cancellationToken).ConfigureAwait(false); if (downloaded) { @@ -55,10 +57,11 @@ namespace MediaBrowser.Providers.MediaInfo bool skipIfAudioTrackMatches, bool requirePerfectMatch, string lang, + string[] disabledSubtitleFetchers, + string[] subtitleFetcherOrder, CancellationToken cancellationToken) { - if (video.LocationType != LocationType.FileSystem || - video.VideoType != VideoType.VideoFile) + if (video.VideoType != VideoType.VideoFile) { return Task.FromResult(false); } @@ -85,7 +88,7 @@ namespace MediaBrowser.Providers.MediaInfo } return DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, - requirePerfectMatch, lang, mediaType, cancellationToken); + requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, mediaType, cancellationToken); } private async Task<bool> DownloadSubtitles(Video video, @@ -94,6 +97,8 @@ namespace MediaBrowser.Providers.MediaInfo bool skipIfAudioTrackMatches, bool requirePerfectMatch, string language, + string[] disabledSubtitleFetchers, + string[] subtitleFetcherOrder, VideoContentType mediaType, CancellationToken cancellationToken) { @@ -140,7 +145,9 @@ namespace MediaBrowser.Providers.MediaInfo // Stop as soon as we find something SearchAllProviders = false, - IsPerfectMatch = requirePerfectMatch + IsPerfectMatch = requirePerfectMatch, + DisabledSubtitleFetchers = disabledSubtitleFetchers, + SubtitleFetcherOrder = subtitleFetcherOrder }; var episode = video as Episode; @@ -159,8 +166,7 @@ namespace MediaBrowser.Providers.MediaInfo if (result != null) { - await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken) - .ConfigureAwait(false); + await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken).ConfigureAwait(false); return true; } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 4e264bd223..a06509b564 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -40,13 +40,18 @@ namespace MediaBrowser.Providers.MediaInfo { var streams = new List<MediaStream>(); - GetExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + if (!video.IsFileProtocol) + { + return streams; + } + + AddExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); startIndex += streams.Count; try { - GetExternalSubtitleStreams(streams, video.GetInternalMetadataPath(), video.Path, startIndex, directoryService, clearCache); + AddExternalSubtitleStreams(streams, video.GetInternalMetadataPath(), video.Path, startIndex, directoryService, clearCache); } catch (IOException) { @@ -60,10 +65,15 @@ namespace MediaBrowser.Providers.MediaInfo IDirectoryService directoryService, bool clearCache) { - var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache); - var list = new List<string>(); + if (!video.IsFileProtocol) + { + return list; + } + + var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache); + foreach (var stream in streams) { list.Add(stream.Path); @@ -72,17 +82,25 @@ namespace MediaBrowser.Providers.MediaInfo return list; } - private void GetExternalSubtitleStreams(List<MediaStream> streams, string folder, + private void AddExternalSubtitleStreams(List<MediaStream> streams, string folder, string videoPath, int startIndex, IDirectoryService directoryService, bool clearCache) { + var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray(); + + AddExternalSubtitleStreams(streams, videoPath, startIndex, files); + } + + public void AddExternalSubtitleStreams(List<MediaStream> streams, + string videoPath, + int startIndex, + string[] files) + { var videoFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(videoPath); videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension); - var files = directoryService.GetFilePaths(folder, clearCache); - foreach (var fullName in files) { var extension = Path.GetExtension(fullName); @@ -131,16 +149,13 @@ namespace MediaBrowser.Providers.MediaInfo var language = fileNameWithoutExtension .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase) .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase) .Split('.') .LastOrDefault(); // Try to translate to three character code // Be flexible and check against both the full and three character versions - var culture = _localization.GetCultures() - .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); + var culture = _localization.FindLanguageInfo(language); if (culture != null) { diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 4d68735b94..6b4181dfb5 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -64,56 +64,69 @@ namespace MediaBrowser.Providers.MediaInfo { var options = GetOptions(); - var types = new List<string>(); - - if (options.DownloadEpisodeSubtitles) - { - types.Add("Episode"); - } - if (options.DownloadMovieSubtitles) - { - types.Add("Movie"); - } - - if (types.Count == 0) - { - return; - } + var types = new[] { "Episode", "Movie" }; var dict = new Dictionary<Guid, BaseItem>(); - foreach (var lang in options.DownloadLanguages) + foreach (var library in _libraryManager.RootFolder.Children.ToList()) { - var query = new InternalItemsQuery - { - MediaTypes = new string[] {MediaType.Video}, - IsVirtualItem = false, - IncludeItemTypes = types.ToArray(types.Count), - DtoOptions = new DtoOptions(true), - SourceTypes = new[] {SourceType.Library} - }; - - if (options.SkipIfAudioTrackMatches) - { - query.HasNoAudioTrackWithLanguage = lang; - } + var libraryOptions = _libraryManager.GetLibraryOptions(library); - if (options.SkipIfEmbeddedSubtitlesPresent) + string[] subtitleDownloadLanguages; + bool SkipIfEmbeddedSubtitlesPresent; + bool SkipIfAudioTrackMatches; + bool RequirePerfectMatch; + + if (libraryOptions.SubtitleDownloadLanguages == null) { - // Exclude if it already has any subtitles of the same language - query.HasNoSubtitleTrackWithLanguage = lang; + subtitleDownloadLanguages = options.DownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + RequirePerfectMatch = options.RequirePerfectMatch; } else { - // Exclude if it already has external subtitles of the same language - query.HasNoExternalSubtitleTrackWithLanguage = lang; + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } - var videosByLanguage = _libraryManager.GetItemList(query); - - foreach (var video in videosByLanguage) + foreach (var lang in subtitleDownloadLanguages) { - dict[video.Id] = video; + var query = new InternalItemsQuery + { + MediaTypes = new string[] { MediaType.Video }, + IsVirtualItem = false, + IncludeItemTypes = types, + DtoOptions = new DtoOptions(true), + SourceTypes = new[] { SourceType.Library }, + Parent = library, + Recursive = true + }; + + if (SkipIfAudioTrackMatches) + { + query.HasNoAudioTrackWithLanguage = lang; + } + + if (SkipIfEmbeddedSubtitlesPresent) + { + // Exclude if it already has any subtitles of the same language + query.HasNoSubtitleTrackWithLanguage = lang; + } + else + { + // Exclude if it already has external subtitles of the same language + query.HasNoExternalSubtitleTrackWithLanguage = lang; + } + + var videosByLanguage = _libraryManager.GetItemList(query); + + foreach (var video in videosByLanguage) + { + dict[video.Id] = video; + } } } @@ -149,34 +162,50 @@ namespace MediaBrowser.Providers.MediaInfo private async Task<bool> DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken) { - if ((options.DownloadEpisodeSubtitles && - video is Episode) || - (options.DownloadMovieSubtitles && - video is Movie)) + var mediaStreams = video.GetMediaStreams(); + + var libraryOptions = _libraryManager.GetLibraryOptions(video); + + string[] subtitleDownloadLanguages; + bool SkipIfEmbeddedSubtitlesPresent; + bool SkipIfAudioTrackMatches; + bool RequirePerfectMatch; + + if (libraryOptions.SubtitleDownloadLanguages == null) { - var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams; - - var downloadedLanguages = await new SubtitleDownloader(_logger, - _subtitleManager) - .DownloadSubtitles(video, - mediaStreams, - options.SkipIfEmbeddedSubtitlesPresent, - options.SkipIfAudioTrackMatches, - options.RequirePerfectMatch, - options.DownloadLanguages, - cancellationToken).ConfigureAwait(false); - - // Rescan - if (downloadedLanguages.Count > 0) - { - await video.RefreshMetadata(cancellationToken).ConfigureAwait(false); - return false; - } + subtitleDownloadLanguages = options.DownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + RequirePerfectMatch = options.RequirePerfectMatch; + } + else + { + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; + SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + } - return true; + var downloadedLanguages = await new SubtitleDownloader(_logger, + _subtitleManager) + .DownloadSubtitles(video, + mediaStreams, + SkipIfEmbeddedSubtitlesPresent, + SkipIfAudioTrackMatches, + RequirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); + + // Rescan + if (downloadedLanguages.Count > 0) + { + await video.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return false; } - return false; + return true; } public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 9fde9c70fa..00ffe383f8 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -29,12 +29,12 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { ImageType.Primary }; } - public Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken) + public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var video = (Video)item; @@ -62,11 +62,9 @@ namespace MediaBrowser.Providers.MediaInfo public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { - var protocol = item.LocationType == LocationType.Remote - ? MediaProtocol.Http - : MediaProtocol.File; + var protocol = item.PathProtocol ?? MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, protocol, null, item.GetPlayableStreamFileNames()); + var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, protocol, null, item.GetPlayableStreamFileNames(_mediaEncoder)); var mediaStreams = item.GetMediaStreams(); @@ -129,11 +127,20 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Screen Grabber"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { + if (item.IsShortcut) + { + return false; + } + if (!item.IsFileProtocol) + { + return false; + } + var video = item as Video; - if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut && video.IsCompleteMedia) + if (video != null && !video.IsPlaceHolder && video.IsCompleteMedia) { return true; } diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 545c3baba6..6d489498e0 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -24,6 +24,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Providers.TV; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Movies { @@ -60,12 +61,12 @@ namespace MediaBrowser.Providers.Movies get { return "FanArt"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Movie || item is BoxSet || item is MusicVideo; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -79,9 +80,9 @@ namespace MediaBrowser.Providers.Movies }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { - var baseItem = (BaseItem)item; + var baseItem = item; var list = new List<RemoteImageInfo>(); var movieId = baseItem.GetProviderId(MetadataProviders.Tmdb); @@ -165,7 +166,6 @@ namespace MediaBrowser.Providers.Movies PopulateImages(list, obj.moviebackground, ImageType.Backdrop, 1920, 1080); } - private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<Image> images, ImageType type, int width, int height) { if (images == null) @@ -189,11 +189,11 @@ namespace MediaBrowser.Providers.Movies Width = width, Height = height, ProviderName = Name, - Url = _regex_http.Replace(url, "https://", 1), + Url = url, Language = i.lang }; - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out likes)) { info.CommunityRating = likes; } @@ -305,7 +305,6 @@ namespace MediaBrowser.Providers.Movies } } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureMovieJson(string id, CancellationToken cancellationToken) { var path = GetFanartJsonPath(id); @@ -314,9 +313,9 @@ namespace MediaBrowser.Providers.Movies if (fileInfo.Exists) { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index a000ed36e4..8a409f9cfe 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -130,11 +130,10 @@ namespace MediaBrowser.Providers.Movies movie.OriginalTitle = movieData.GetOriginalTitle(); - // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException. - movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null; + movie.Overview = string.IsNullOrWhiteSpace(movieData.overview) ? null : WebUtility.HtmlDecode(movieData.overview); movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; - movie.HomePageUrl = movieData.homepage; + //movie.HomePageUrl = movieData.homepage; if (!string.IsNullOrEmpty(movieData.tagline)) { @@ -175,7 +174,6 @@ namespace MediaBrowser.Providers.Movies //movie.VoteCount = movieData.vote_count; - //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match if (movieData.releases != null && movieData.releases.countries != null) { var releases = movieData.releases.countries.Where(i => !string.IsNullOrWhiteSpace(i.certification)).ToList(); @@ -226,7 +224,7 @@ namespace MediaBrowser.Providers.Movies } resultItem.ResetPeople(); - var tmdbImageUrl = settings.images.secure_base_url + "original"; + var tmdbImageUrl = settings.images.GetImageUrl("original"); //Actors, Directors, Writers - all in People //actors come from cast @@ -307,19 +305,14 @@ namespace MediaBrowser.Providers.Movies // movie.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList(); //} - if (movieData.trailers != null && movieData.trailers.youtube != null && - movieData.trailers.youtube.Count > 0) + if (movieData.trailers != null && movieData.trailers.youtube != null) { - var hasTrailers = movie as IHasTrailers; - if (hasTrailers != null) + movie.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl { - hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl - { - Url = string.Format("https://www.youtube.com/watch?v={0}", i.source), - Name = i.name + Url = string.Format("https://www.youtube.com/watch?v={0}", i.source), + Name = i.name - }).ToArray(); - } + }).ToArray(); } } diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index 8dc4bd56ac..ad45c8432b 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -41,12 +41,12 @@ namespace MediaBrowser.Providers.Movies get { return "TheMovieDb"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Movie || item is MusicVideo || item is Trailer; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -55,13 +55,13 @@ namespace MediaBrowser.Providers.Movies }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); var language = item.GetPreferredMetadataLanguage(); - var results = await FetchImages((BaseItem)item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false); + var results = await FetchImages(item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false); if (results == null) { @@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.Movies var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var supportedImages = GetSupportedImages(item).ToList(); diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 06adbffd7c..6266e717ea 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.Movies var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var remoteResult = new RemoteSearchResult { @@ -156,15 +156,17 @@ namespace MediaBrowser.Providers.Movies { using (var json = response.Content) { - _tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json); + _tmdbSettings = await _jsonSerializer.DeserializeFromStreamAsync<TmdbSettingsResult>(json).ConfigureAwait(false); return _tmdbSettings; } } } - private const string TmdbConfigUrl = "https://api.themoviedb.org/3/configuration?api_key={0}"; - private const string GetMovieInfo3 = @"https://api.themoviedb.org/3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers"; + public const string BaseMovieDbUrl = "https://api.themoviedb.org/"; + + private const string TmdbConfigUrl = BaseMovieDbUrl + "3/configuration?api_key={0}"; + private const string GetMovieInfo3 = BaseMovieDbUrl + @"3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers"; internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; @@ -209,7 +211,6 @@ namespace MediaBrowser.Providers.Movies _jsonSerializer.SerializeToFile(mainResult, dataFilePath); } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(tmdbId)) @@ -224,9 +225,9 @@ namespace MediaBrowser.Providers.Movies if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } @@ -354,7 +355,7 @@ namespace MediaBrowser.Providers.Movies { using (var json = response.Content) { - mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json); + mainResult = await _jsonSerializer.DeserializeFromStreamAsync<CompleteMovieData>(json).ConfigureAwait(false); } } } @@ -399,7 +400,7 @@ namespace MediaBrowser.Providers.Movies { using (var json = response.Content) { - var englishResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json); + var englishResult = await _jsonSerializer.DeserializeFromStreamAsync<CompleteMovieData>(json).ConfigureAwait(false); mainResult.overview = englishResult.overview; } @@ -638,7 +639,7 @@ namespace MediaBrowser.Providers.Movies { get { - return 0; + return 1; } } diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs index 673af5534c..3b4a385d4e 100644 --- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Movies public class MovieDbSearch { private static readonly CultureInfo EnUs = new CultureInfo("en-US"); - private const string Search3 = @"https://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}"; + private const string Search3 = MovieDbProvider.BaseMovieDbUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}"; internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Movies var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); if (!string.IsNullOrWhiteSpace(name)) { @@ -164,7 +164,7 @@ namespace MediaBrowser.Providers.Movies { using (var json = response.Content) { - var searchResults = _json.DeserializeFromStream<TmdbMovieSearchResults>(json); + var searchResults = await _json.DeserializeFromStreamAsync<TmdbMovieSearchResults>(json).ConfigureAwait(false); var results = searchResults.results ?? new List<TmdbMovieSearchResult>(); @@ -219,7 +219,7 @@ namespace MediaBrowser.Providers.Movies { using (var json = response.Content) { - var searchResults = _json.DeserializeFromStream<TmdbTvSearchResults>(json); + var searchResults = await _json.DeserializeFromStreamAsync<TmdbTvSearchResults>(json).ConfigureAwait(false); var results = searchResults.results ?? new List<TvResult>(); diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index a6b7bde6fa..208d412f6b 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -9,6 +9,8 @@ namespace MediaBrowser.Providers.Movies { public class MovieDbMovieExternalId : IExternalId { + public const string BaseMovieDbUrl = "https://www.themoviedb.org/"; + public string Name { get { return "TheMovieDb"; } @@ -21,7 +23,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "https://www.themoviedb.org/movie/{0}"; } + get { return BaseMovieDbUrl + "movie/{0}"; } } public bool Supports(IHasProviderIds item) @@ -51,7 +53,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "https://www.themoviedb.org/tv/{0}"; } + get { return MovieDbMovieExternalId.BaseMovieDbUrl + "tv/{0}"; } } public bool Supports(IHasProviderIds item) @@ -74,7 +76,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "https://www.themoviedb.org/collection/{0}"; } + get { return MovieDbMovieExternalId.BaseMovieDbUrl + "collection/{0}"; } } public bool Supports(IHasProviderIds item) @@ -97,7 +99,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "https://www.themoviedb.org/person/{0}"; } + get { return MovieDbMovieExternalId.BaseMovieDbUrl + "person/{0}"; } } public bool Supports(IHasProviderIds item) @@ -120,7 +122,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "https://www.themoviedb.org/collection/{0}"; } + get { return MovieDbMovieExternalId.BaseMovieDbUrl + "collection/{0}"; } } public bool Supports(IHasProviderIds item) @@ -143,7 +145,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "http://www.imdb.com/title/{0}"; } + get { return "https://www.imdb.com/title/{0}"; } } public bool Supports(IHasProviderIds item) @@ -174,7 +176,7 @@ namespace MediaBrowser.Providers.Movies public string UrlFormatString { - get { return "http://www.imdb.com/name/{0}"; } + get { return "https://www.imdb.com/name/{0}"; } } public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index dc3146f2a9..f0674ac9b7 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Movies { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - if (replaceData || target.Item.TrailerTypes.Count == 0) + if (replaceData || target.Item.TrailerTypes.Length == 0) { target.Item.TrailerTypes = source.Item.TrailerTypes; } diff --git a/MediaBrowser.Providers/Movies/TmdbSettings.cs b/MediaBrowser.Providers/Movies/TmdbSettings.cs index 12bb77afc9..facc4a31b9 100644 --- a/MediaBrowser.Providers/Movies/TmdbSettings.cs +++ b/MediaBrowser.Providers/Movies/TmdbSettings.cs @@ -8,6 +8,11 @@ namespace MediaBrowser.Providers.Movies public string secure_base_url { get; set; } public List<string> poster_sizes { get; set; } public List<string> profile_sizes { get; set; } + + public string GetImageUrl(string image) + { + return secure_base_url + image; + } } internal class TmdbSettingsResult diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 48f520681a..338fc1c6d7 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -11,72 +11,69 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Providers.Music { public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo> { - protected override ItemUpdateType BeforeSaveInternal(MusicAlbum item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override IList<BaseItem> GetChildrenForMetadataUpdates(MusicAlbum item) { - var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); + return item.GetRecursiveChildren(i => i is Audio) + .ToList(); + } + + protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { - if (!item.IsLocked) + if (!item.LockedFields.Contains(MetadataFields.Name)) { - var songs = item.GetRecursiveChildren(i => i is Audio) - .Cast<Audio>() - .ToList(); + var name = children.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i)); - if (!item.LockedFields.Contains(MetadataFields.Genres)) + if (!string.IsNullOrEmpty(name)) { - var currentList = item.Genres.ToList(); - - item.Genres = songs.SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) + if (!string.Equals(item.Name, name, StringComparison.Ordinal)) { + item.Name = name; updateType = updateType | ItemUpdateType.MetadataEdit; } } + } - if (!item.LockedFields.Contains(MetadataFields.Studios)) - { - var currentList = item.Studios; - - item.Studios = songs.SelectMany(i => i.Studios) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); + var songs = children.Cast<Audio>().ToArray(); - if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) - { - updateType = updateType | ItemUpdateType.MetadataEdit; - } - } + updateType = updateType | SetAlbumArtistFromSongs(item, songs); + updateType = updateType | SetArtistsFromSongs(item, songs); + } - if (!item.LockedFields.Contains(MetadataFields.Name)) - { - var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i)); + return updateType; + } - if (!string.IsNullOrEmpty(name)) - { - if (!string.Equals(item.Name, name, StringComparison.Ordinal)) - { - item.Name = name; - updateType = updateType | ItemUpdateType.MetadataEdit; - } - } - } + protected override bool EnableUpdatingPremiereDateFromChildren + { + get + { + return true; + } + } - updateType = updateType | SetAlbumArtistFromSongs(item, songs); - updateType = updateType | SetArtistsFromSongs(item, songs); - updateType = updateType | SetDateFromSongs(item, songs); - } + protected override bool EnableUpdatingGenresFromChildren + { + get + { + return true; } + } - return updateType; + protected override bool EnableUpdatingStudiosFromChildren + { + get + { + return true; + } } private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable<Audio> songs) @@ -117,40 +114,6 @@ namespace MediaBrowser.Providers.Music return updateType; } - private ItemUpdateType SetDateFromSongs(MusicAlbum item, List<Audio> songs) - { - var updateType = ItemUpdateType.None; - - var date = songs.Select(i => i.PremiereDate) - .FirstOrDefault(i => i.HasValue); - - var originalPremiereDate = item.PremiereDate; - var originalProductionYear = item.ProductionYear; - - if (date.HasValue) - { - item.PremiereDate = date.Value; - item.ProductionYear = date.Value.Year; - } - else - { - var year = songs.Select(i => i.ProductionYear ?? 1800).FirstOrDefault(i => i != 1800); - - if (year != 1800) - { - item.ProductionYear = year; - } - } - - if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) || - (originalProductionYear ?? -1) != (item.ProductionYear ?? -1)) - { - updateType = updateType | ItemUpdateType.MetadataEdit; - } - - return updateType; - } - protected override void MergeData(MetadataResult<MusicAlbum> source, MetadataResult<MusicAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index 70ae525445..7741a3f433 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -10,41 +10,29 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Providers.Music { public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo> { - protected override ItemUpdateType BeforeSaveInternal(MusicArtist item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override IList<BaseItem> GetChildrenForMetadataUpdates(MusicArtist item) { - var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); - - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) - { - if (!item.IsLocked && !item.LockedFields.Contains(MetadataFields.Genres)) + return item.IsAccessedByName ? + item.GetTaggedItems(new Controller.Entities.InternalItemsQuery { - var taggedItems = item.IsAccessedByName ? - item.GetTaggedItems(new Controller.Entities.InternalItemsQuery() - { - Recursive = true, - IsFolder = false - }) : - item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); - - var currentList = item.Genres; - - item.Genres = taggedItems.SelectMany(i => i.Genres) - .DistinctNames() - .ToList(); + Recursive = true, + IsFolder = false + }) : + item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); + } - if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) - { - updateType = updateType | ItemUpdateType.MetadataEdit; - } - } + protected override bool EnableUpdatingGenresFromChildren + { + get + { + return true; } - - return updateType; } protected override void MergeData(MetadataResult<MusicArtist> source, MetadataResult<MusicArtist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs index 0ce221b065..273962c6ac 100644 --- a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Music _json = json; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); @@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.Music } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is MusicAlbum; } diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs index 1082685a88..aaf0ece3db 100644 --- a/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrEmpty(result.strGenre)) { - item.Genres = new List<string> { result.strGenre }; + item.Genres = new [] { result.strGenre }; } item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist); @@ -133,7 +133,6 @@ namespace MediaBrowser.Providers.Music get { return "TheAudioDB"; } } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) { var xmlPath = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); @@ -142,9 +141,9 @@ namespace MediaBrowser.Providers.Music if (fileInfo.Exists) { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } diff --git a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs index dded509e2f..89635872f2 100644 --- a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Music _httpClient = httpClient; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); @@ -138,7 +138,7 @@ namespace MediaBrowser.Providers.Music get { return "TheAudioDB"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is MusicArtist; } diff --git a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs index 66d6889595..e0d193ff16 100644 --- a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Music public static AudioDbArtistProvider Current; private const string ApiKey = "49jhsf8248yfahka89724011"; - public const string BaseUrl = "http://www.theaudiodb.com/api/v1/json/" + ApiKey; + public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json) { @@ -71,11 +71,11 @@ namespace MediaBrowser.Providers.Music private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage) { - item.HomePageUrl = result.strWebsite; + //item.HomePageUrl = result.strWebsite; if (!string.IsNullOrEmpty(result.strGenre)) { - item.Genres = new List<string> { result.strGenre }; + item.Genres = new[] { result.strGenre }; } item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist); @@ -121,7 +121,6 @@ namespace MediaBrowser.Providers.Music get { return "TheAudioDB"; } } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken) { var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); @@ -130,9 +129,9 @@ namespace MediaBrowser.Providers.Music if (fileInfo.Exists) { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } diff --git a/MediaBrowser.Providers/Music/AudioDbExternalIds.cs b/MediaBrowser.Providers/Music/AudioDbExternalIds.cs index 885aa61d84..fcb7c338db 100644 --- a/MediaBrowser.Providers/Music/AudioDbExternalIds.cs +++ b/MediaBrowser.Providers/Music/AudioDbExternalIds.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Music public string UrlFormatString { - get { return "http://www.theaudiodb.com/album/{0}"; } + get { return "https://www.theaudiodb.com/album/{0}"; } } public bool Supports(IHasProviderIds item) @@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.Music public string UrlFormatString { - get { return "http://www.theaudiodb.com/album/{0}"; } + get { return "https://www.theaudiodb.com/album/{0}"; } } public bool Supports(IHasProviderIds item) @@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.Music public string UrlFormatString { - get { return "http://www.theaudiodb.com/artist/{0}"; } + get { return "https://www.theaudiodb.com/artist/{0}"; } } public bool Supports(IHasProviderIds item) @@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Music public string UrlFormatString { - get { return "http://www.theaudiodb.com/artist/{0}"; } + get { return "https://www.theaudiodb.com/artist/{0}"; } } public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index 7a6826d319..2f7d70be0f 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -18,6 +18,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Music { @@ -47,12 +48,12 @@ namespace MediaBrowser.Providers.Music get { return "FanArt"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is MusicAlbum; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -61,7 +62,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var album = (MusicAlbum)item; @@ -153,7 +154,6 @@ namespace MediaBrowser.Providers.Music } } - private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartArtistProvider.FanartArtistImage> images, ImageType type, @@ -181,11 +181,11 @@ namespace MediaBrowser.Providers.Music Width = width, Height = height, ProviderName = Name, - Url = _regex_http.Replace(url, "https://", 1), + Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase), Language = i.lang }; - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out likes)) { info.CommunityRating = likes; } diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index c06ee9d738..99a717ca18 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Music { @@ -58,12 +59,12 @@ namespace MediaBrowser.Providers.Music get { return "FanArt"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is MusicArtist; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -75,7 +76,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var artist = (MusicArtist)item; @@ -151,7 +152,6 @@ namespace MediaBrowser.Providers.Music PopulateImages(list, obj.musicarts, ImageType.Art, 500, 281); } - private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartArtistImage> images, ImageType type, @@ -179,11 +179,11 @@ namespace MediaBrowser.Providers.Music Width = width, Height = height, ProviderName = Name, - Url = _regex_http.Replace(url, "https://", 1), + Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase), Language = i.lang }; - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out likes)) { info.CommunityRating = likes; } @@ -209,7 +209,6 @@ namespace MediaBrowser.Providers.Music }); } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureArtistJson(string musicBrainzId, CancellationToken cancellationToken) { var jsonPath = GetArtistJsonPath(_config.ApplicationPaths, musicBrainzId); @@ -218,9 +217,9 @@ namespace MediaBrowser.Providers.Music if (fileInfo.Exists) { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 3befca4289..c676f91982 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -75,8 +75,11 @@ namespace MediaBrowser.Providers.Music { isNameSearch = true; + // I'm sure there is a better way but for now it resolves search for 12" Mixes + var queryName = searchInfo.Name.Replace("\"", string.Empty); + url = string.Format("/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(searchInfo.Name), + WebUtility.UrlEncode(queryName), WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); } } @@ -296,7 +299,7 @@ namespace MediaBrowser.Providers.Music public string Overview; public int? Year; - public List<Tuple<string, string>> Artists = new List<Tuple<string, string>>(); + public List<ValueTuple<string, string>> Artists = new List<ValueTuple<string, string>>(); public static List<ReleaseResult> Parse(XmlReader reader) { @@ -450,7 +453,7 @@ namespace MediaBrowser.Providers.Music { var artist = ParseArtistCredit(subReader); - if (artist != null) + if (!string.IsNullOrEmpty(artist.Item1)) { result.Artists.Add(artist); } @@ -475,7 +478,7 @@ namespace MediaBrowser.Providers.Music } } - private static Tuple<string, string> ParseArtistCredit(XmlReader reader) + private static ValueTuple<string, string> ParseArtistCredit(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -509,10 +512,10 @@ namespace MediaBrowser.Providers.Music } } - return null; + return new ValueTuple<string, string>(); } - private static Tuple<string, string> ParseArtistNameCredit(XmlReader reader) + private static ValueTuple<string, string> ParseArtistNameCredit(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -549,15 +552,10 @@ namespace MediaBrowser.Providers.Music } } - if (string.IsNullOrWhiteSpace(name)) - { - return null; - } - - return new Tuple<string, string>(name, null); + return new ValueTuple<string, string>(name, null); } - private static Tuple<string, string> ParseArtistArtistCredit(XmlReader reader, string artistId) + private static ValueTuple<string, string> ParseArtistArtistCredit(XmlReader reader, string artistId) { reader.MoveToContent(); reader.Read(); @@ -591,12 +589,7 @@ namespace MediaBrowser.Providers.Music } } - if (string.IsNullOrWhiteSpace(name)) - { - return null; - } - - return new Tuple<string, string>(name, artistId); + return new ValueTuple<string, string>(name, artistId); } private async Task<string> GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) @@ -753,45 +746,43 @@ namespace MediaBrowser.Providers.Music private async Task<List<MbzUrl>> RefreshMzbUrls(bool forceMusicBrainzProper = false) { - List<MbzUrl> list; + List<MbzUrl> list = null; - if (forceMusicBrainzProper) - { - list = new List<MbzUrl> - { - new MbzUrl - { - url = MusicBrainzBaseUrl, - throttleMs = 1000 - } - }; - } - else + if (!forceMusicBrainzProper) { - try + var musicbrainzadminurl = _appHost.GetValue("musicbrainzadminurl"); + + if (!string.IsNullOrEmpty(musicbrainzadminurl)) { - var options = new HttpRequestOptions + try { - Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls", - UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion - }; + var options = new HttpRequestOptions + { + Url = musicbrainzadminurl, + UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion + }; - using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) - { - using (var stream = response.Content) + using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) { - var results = _json.DeserializeFromStream<List<MbzUrl>>(stream); + using (var stream = response.Content) + { + var results = await _json.DeserializeFromStreamAsync<List<MbzUrl>>(stream).ConfigureAwait(false); - list = results; + list = results; + } } + _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting music brainz info", ex); } - _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; } - catch (Exception ex) - { - _logger.ErrorException("Error getting music brainz info", ex); + } - list = new List<MbzUrl> + if (list == null) + { + list = new List<MbzUrl> { new MbzUrl { @@ -799,7 +790,6 @@ namespace MediaBrowser.Providers.Music throttleMs = 1000 } }; - } } _mbzUrls = list.ToList(); diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index f514791a7a..8e2a47100d 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Music // They seem to throw bad request failures on any term with a slash var nameToSearch = searchInfo.Name.Replace('/', ' '); - var url = String.Format("/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch)); + var url = String.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) { diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 814488df17..4d026ea0fc 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Providers.Music { @@ -141,4 +142,27 @@ namespace MediaBrowser.Providers.Music return item is Audio; } } + + public class ImvdbId : IExternalId + { + public string Name + { + get { return "IMVDb"; } + } + + public string Key + { + get { return "IMVDb"; } + } + + public string UrlFormatString + { + get { return null; } + } + + public bool Supports(IHasProviderIds item) + { + return item is MusicVideo; + } + } } diff --git a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs index 1ef420dddc..1eb9b31039 100644 --- a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs @@ -12,8 +12,7 @@ using MediaBrowser.Model.Serialization; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; +using MediaBrowser.Common; namespace MediaBrowser.Providers.Omdb { @@ -23,16 +22,18 @@ namespace MediaBrowser.Providers.Omdb private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; - public OmdbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; _fileSystem = fileSystem; _configurationManager = configurationManager; + _appHost = appHost; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -40,13 +41,13 @@ namespace MediaBrowser.Providers.Omdb }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var imdbId = item.GetProviderId(MetadataProviders.Imdb); var list = new List<RemoteImageInfo>(); - var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager); + var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager); if (!string.IsNullOrWhiteSpace(imdbId)) { @@ -56,7 +57,7 @@ namespace MediaBrowser.Providers.Omdb { if (item is Episode) { - // img.omdbapi.com returning 404's + // img.omdbapi.com is returning 404's list.Add(new RemoteImageInfo { ProviderName = Name, @@ -91,7 +92,7 @@ namespace MediaBrowser.Providers.Omdb get { return "The Open Movie Database"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Movie || item is Trailer || item is Episode; } diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs index c1b98dfbf8..8c7ef71b61 100644 --- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; namespace MediaBrowser.Providers.Omdb { @@ -30,8 +31,9 @@ namespace MediaBrowser.Providers.Omdb private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; - public OmdbItemProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbItemProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; @@ -39,6 +41,7 @@ namespace MediaBrowser.Providers.Omdb _libraryManager = libraryManager; _fileSystem = fileSystem; _configurationManager = configurationManager; + _appHost = appHost; } public int Order @@ -46,7 +49,7 @@ namespace MediaBrowser.Providers.Omdb get { // After primary option - return 1; + return 2; } } @@ -124,7 +127,7 @@ namespace MediaBrowser.Providers.Omdb } } - var url = OmdbProvider.GetOmdbUrl(urlQuery, cancellationToken); + var url = OmdbProvider.GetOmdbUrl(urlQuery, _appHost, cancellationToken); using (var response = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { @@ -134,7 +137,7 @@ namespace MediaBrowser.Providers.Omdb if (isSearch) { - var searchResultList = _jsonSerializer.DeserializeFromStream<SearchResultList>(stream); + var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false); if (searchResultList != null && searchResultList.Search != null) { resultList.AddRange(searchResultList.Search); @@ -142,7 +145,7 @@ namespace MediaBrowser.Providers.Omdb } else { - var result = _jsonSerializer.DeserializeFromStream<SearchResult>(stream); + var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false); if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase)) { resultList.Add(result); @@ -168,7 +171,7 @@ namespace MediaBrowser.Providers.Omdb int parsedYear; if (result.Year.Length > 0 - && int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Any, CultureInfo.InvariantCulture, out parsedYear)) + && int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedYear)) { item.ProductionYear = parsedYear; } @@ -226,7 +229,7 @@ namespace MediaBrowser.Providers.Omdb result.Item.SetProviderId(MetadataProviders.Imdb, imdbId); result.HasMetadata = true; - await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); } return result; @@ -258,7 +261,7 @@ namespace MediaBrowser.Providers.Omdb result.Item.SetProviderId(MetadataProviders.Imdb, imdbId); result.HasMetadata = true; - await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); } return result; diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index 2cac449446..5c4eb62a8b 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -14,6 +14,7 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; namespace MediaBrowser.Providers.Omdb { @@ -24,13 +25,15 @@ namespace MediaBrowser.Providers.Omdb private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClient _httpClient; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IApplicationHost _appHost; - public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; _fileSystem = fileSystem; _configurationManager = configurationManager; + _appHost = appHost; } public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken) @@ -90,10 +93,10 @@ namespace MediaBrowser.Providers.Omdb item.CommunityRating = imdbRating; } - if (!string.IsNullOrEmpty(result.Website)) - { - item.HomePageUrl = result.Website; - } + //if (!string.IsNullOrEmpty(result.Website)) + //{ + // item.HomePageUrl = result.Website; + //} if (!string.IsNullOrWhiteSpace(result.imdbID)) { @@ -197,10 +200,10 @@ namespace MediaBrowser.Providers.Omdb item.CommunityRating = imdbRating; } - if (!string.IsNullOrEmpty(result.Website)) - { - item.HomePageUrl = result.Website; - } + //if (!string.IsNullOrEmpty(result.Website)) + //{ + // item.HomePageUrl = result.Website; + //} if (!string.IsNullOrWhiteSpace(result.imdbID)) { @@ -265,9 +268,16 @@ namespace MediaBrowser.Providers.Omdb return false; } - public static string GetOmdbUrl(string query, CancellationToken cancellationToken) + public static string GetOmdbUrl(string query, IApplicationHost appHost, CancellationToken cancellationToken) { - var url = "https://www.omdbapi.com?apikey=fe53f97e"; + var baseUrl = appHost.GetValue("omdb_baseurl"); + + if (string.IsNullOrEmpty(baseUrl)) + { + baseUrl = "https://www.omdbapi.com"; + } + + var url = baseUrl + "?apikey=fe53f97e"; if (!string.IsNullOrWhiteSpace(query)) { @@ -293,19 +303,19 @@ namespace MediaBrowser.Providers.Omdb if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1) { return path; } } - var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), cancellationToken); + var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), _appHost, cancellationToken); using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { - var rootObject = _jsonSerializer.DeserializeFromStream<RootObject>(stream); + var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); _jsonSerializer.SerializeToFile(rootObject, path); } @@ -330,19 +340,19 @@ namespace MediaBrowser.Providers.Omdb if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1) { return path; } } - var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), cancellationToken); + var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), _appHost, cancellationToken); using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { - var rootObject = _jsonSerializer.DeserializeFromStream<SeasonRootObject>(stream); + var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); _jsonSerializer.SerializeToFile(rootObject, path); } @@ -401,7 +411,7 @@ namespace MediaBrowser.Providers.Omdb // But only do it if english is the preferred language because this data will not be localized if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre)) { - item.Genres.Clear(); + item.Genres = Array.Empty<string>(); foreach (var genre in result.Genre .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) diff --git a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs index 4dfcdba09f..d4c8289e5b 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs @@ -37,12 +37,12 @@ namespace MediaBrowser.Providers.People get { return "TheMovieDb"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Person; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.People }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var person = (Person)item; var id = person.GetProviderId(MetadataProviders.Tmdb); @@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.People var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl); } diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs index c921df61ca..7af058bcdc 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.People var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); if (!string.IsNullOrEmpty(tmdbId)) { @@ -89,7 +89,7 @@ namespace MediaBrowser.Providers.People return new List<RemoteSearchResult>(); } - var url = string.Format(@"https://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey); + var url = string.Format(MovieDbProvider.BaseMovieDbUrl + @"3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey); using (var response = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions { @@ -101,7 +101,7 @@ namespace MediaBrowser.Providers.People { using (var json = response.Content) { - var result = _jsonSerializer.DeserializeFromStream<PersonSearchResults>(json) ?? + var result = await _jsonSerializer.DeserializeFromStreamAsync<PersonSearchResults>(json).ConfigureAwait(false) ?? new PersonSearchResults(); return result.Results.Select(i => GetSearchResult(i, tmdbImageUrl)); @@ -164,7 +164,7 @@ namespace MediaBrowser.Providers.People // TODO: This should go in PersonMetadataService, not each person provider item.Name = id.Name; - item.HomePageUrl = info.homepage; + //item.HomePageUrl = info.homepage; if (!string.IsNullOrWhiteSpace(info.place_of_birth)) { @@ -219,12 +219,12 @@ namespace MediaBrowser.Providers.People var fileInfo = _fileSystem.GetFileSystemInfo(dataFilePath); - if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { return; } - var url = string.Format(@"https://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id); + var url = string.Format(MovieDbProvider.BaseMovieDbUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id); using (var response = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions { diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs index 2c3ba00c82..13cdc79fbe 100644 --- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs @@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.People get { return "TheTVDB"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Person; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -61,12 +61,12 @@ namespace MediaBrowser.Providers.People }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var seriesWithPerson = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Series).Name }, - PersonIds = new[] { item.Id.ToString("N") }, + PersonIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { EnableImages = false diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs new file mode 100644 index 0000000000..da4873ca47 --- /dev/null +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -0,0 +1,189 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Playlists; +using System.IO; +using PlaylistsNET; +using PlaylistsNET.Content; +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Playlists +{ + public class PlaylistItemsProvider : ICustomMetadataProvider<Playlist>, + IHasOrder, + IForcedProvider, + IPreRefreshProvider, + IHasItemChangeMonitor + { + private ILogger _logger; + private IFileSystem _fileSystem; + + public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger) + { + _fileSystem = fileSystem; + _logger = logger; + } + + public string Name + { + get { return "Playlist Reader"; } + } + + public Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + var path = item.Path; + if (!Playlist.IsPlaylistFile(path)) + { + return Task.FromResult(ItemUpdateType.None); + } + + var extension = Path.GetExtension(path); + if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return Task.FromResult(ItemUpdateType.None); + } + + using (var stream = _fileSystem.OpenRead(path)) + { + var items = GetItems(stream, extension).ToArray(); + + item.LinkedChildren = items; + } + + return Task.FromResult(ItemUpdateType.None); + } + + private IEnumerable<LinkedChild> GetItems(Stream stream, string extension) + { + if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase)) + { + return GetWplItems(stream); + } + if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase)) + { + return GetZplItems(stream); + } + if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) + { + return GetM3uItems(stream); + } + if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) + { + return GetM3u8Items(stream); + } + if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) + { + return GetPlsItems(stream); + } + + return new List<LinkedChild>(); + } + + private IEnumerable<LinkedChild> GetPlsItems(Stream stream) + { + var content = new PlsContent(); + var playlist = content.GetFromStream(stream); + + return playlist.PlaylistEntries.Select(i => new LinkedChild + { + Path = i.Path, + Type = LinkedChildType.Manual + }); + } + + private IEnumerable<LinkedChild> GetM3u8Items(Stream stream) + { + var content = new M3u8Content(); + var playlist = content.GetFromStream(stream); + + return playlist.PlaylistEntries.Select(i => new LinkedChild + { + Path = i.Path, + Type = LinkedChildType.Manual + }); + } + + private IEnumerable<LinkedChild> GetM3uItems(Stream stream) + { + var content = new M3uContent(); + var playlist = content.GetFromStream(stream); + + return playlist.PlaylistEntries.Select(i => new LinkedChild + { + Path = i.Path, + Type = LinkedChildType.Manual + }); + } + + private IEnumerable<LinkedChild> GetZplItems(Stream stream) + { + var content = new ZplContent(); + var playlist = content.GetFromStream(stream); + + return playlist.PlaylistEntries.Select(i => new LinkedChild + { + Path = i.Path, + Type = LinkedChildType.Manual + }); + } + + private IEnumerable<LinkedChild> GetWplItems(Stream stream) + { + WplContent content = new WplContent(); + var playlist = content.GetFromStream(stream); + + return playlist.PlaylistEntries.Select(i => new LinkedChild + { + Path = i.Path, + Type = LinkedChildType.Manual + }); + } + + public bool HasChanged(BaseItem item, IDirectoryService directoryService) + { + var path = item.Path; + + if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol) + { + var file = directoryService.GetFile(path); + if (file != null && file.LastWriteTimeUtc != item.DateModified) + { + _logger.Debug("Refreshing {0} due to date modified timestamp change.", path); + return true; + } + } + + return false; + } + + public int Order + { + get + { + // Run last + return 100; + } + } + } +} diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index a81facfb51..1d65917921 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -10,11 +10,17 @@ using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Providers.Playlists { class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo> { + protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item) + { + return item.GetLinkedChildren(); + } + protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); @@ -22,44 +28,40 @@ namespace MediaBrowser.Providers.Playlists var sourceItem = source.Item; var targetItem = target.Item; - if (replaceData || string.IsNullOrEmpty(targetItem.PlaylistMediaType)) - { - targetItem.PlaylistMediaType = sourceItem.PlaylistMediaType; - } - if (mergeMetadataSettings) { + targetItem.PlaylistMediaType = sourceItem.PlaylistMediaType; targetItem.LinkedChildren = sourceItem.LinkedChildren; targetItem.Shares = sourceItem.Shares; } } - protected override ItemUpdateType BeforeSaveInternal(Playlist item, bool isFullRefresh, ItemUpdateType currentUpdateType) + public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { - var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); + } - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + protected override bool EnableUpdatingGenresFromChildren + { + get { - if (!item.IsLocked && !item.LockedFields.Contains(MetadataFields.Genres)) - { - var currentList = item.Genres; - - item.Genres = item.GetLinkedChildren().SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) - { - updateType = updateType | ItemUpdateType.MetadataEdit; - } - } + return true; } + } - return updateType; + protected override bool EnableUpdatingOfficialRatingFromChildren + { + get + { + return true; + } } - public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + protected override bool EnableUpdatingStudiosFromChildren { + get + { + return true; + } } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index f63615430f..2949c903fc 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -4,13 +4,14 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.ImagesByName; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using System; +using MediaBrowser.Common.Progress; namespace MediaBrowser.Providers.Studios { @@ -29,20 +30,15 @@ namespace MediaBrowser.Providers.Studios public string Name { - get { return ProviderName; } - } - - public static string ProviderName - { get { return "Emby Designs"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Studio; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -51,12 +47,12 @@ namespace MediaBrowser.Providers.Studios }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { return GetImages(item, true, true, cancellationToken); } - private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, bool posters, bool thumbs, CancellationToken cancellationToken) + private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, bool posters, bool thumbs, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); @@ -83,11 +79,11 @@ namespace MediaBrowser.Providers.Studios return list.Where(i => i != null); } - private RemoteImageInfo GetImage(IHasMetadata item, string filename, ImageType type, string remoteFilename) + private RemoteImageInfo GetImage(BaseItem item, string filename, ImageType type, string remoteFilename) { - var list = ImageUtils.GetAvailableImages(filename, _fileSystem); + var list = GetAvailableImages(filename, _fileSystem); - var match = ImageUtils.FindMatch(item, list); + var match = FindMatch(item, list); if (!string.IsNullOrEmpty(match)) { @@ -113,14 +109,14 @@ namespace MediaBrowser.Providers.Studios { const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studiothumbs.txt"; - return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, cancellationToken); + return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken); } private Task<string> EnsurePosterList(string file, CancellationToken cancellationToken) { const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studioposters.txt"; - return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, cancellationToken); + return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken); } public int Order @@ -137,5 +133,86 @@ namespace MediaBrowser.Providers.Studios BufferContent = false }); } + + /// <summary> + /// Ensures the list. + /// </summary> + /// <param name="url">The URL.</param> + /// <param name="file">The file.</param> + /// <param name="httpClient">The HTTP client.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task<string> EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, CancellationToken cancellationToken) + { + var fileInfo = fileSystem.GetFileInfo(file); + + if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) + { + var temp = await httpClient.GetTempFile(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Progress = new SimpleProgress<double>(), + Url = url + + }).ConfigureAwait(false); + + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(file)); + + try + { + fileSystem.CopyFile(temp, file, true); + } + catch + { + + } + + return temp; + } + + return file; + } + + public string FindMatch(BaseItem item, IEnumerable<string> images) + { + var name = GetComparableName(item.Name); + + return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase)); + } + + private string GetComparableName(string name) + { + return name.Replace(" ", string.Empty) + .Replace(".", string.Empty) + .Replace("&", string.Empty) + .Replace("!", string.Empty) + .Replace(",", string.Empty) + .Replace("/", string.Empty); + } + + public IEnumerable<string> GetAvailableImages(string file, IFileSystem fileSystem) + { + using (var fileStream = fileSystem.GetFileStream(file, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) + { + using (var reader = new StreamReader(fileStream)) + { + var lines = new List<string>(); + + while (!reader.EndOfStream) + { + var text = reader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(text)) + { + lines.Add(text); + } + } + + return lines; + } + } + } + } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 6a9b7136c0..071687c0fa 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -19,6 +19,8 @@ using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Providers.Subtitles { @@ -28,33 +30,56 @@ namespace MediaBrowser.Providers.Subtitles private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _monitor; - private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _config; public event EventHandler<SubtitleDownloadEventArgs> SubtitlesDownloaded; public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure; - public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager config) + private ILocalizationManager _localization; + + public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, IMediaSourceManager mediaSourceManager, IServerConfigurationManager config, ILocalizationManager localizationManager) { _logger = logger; _fileSystem = fileSystem; _monitor = monitor; - _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; _config = config; + _localization = localizationManager; } public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders) { - _subtitleProviders = subtitleProviders.ToArray(); + _subtitleProviders = subtitleProviders + .OrderBy(i => + { + var hasOrder = i as IHasOrder; + return hasOrder == null ? 0 : hasOrder.Order; + }) + .ToArray(); } public async Task<RemoteSubtitleInfo[]> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken) { + if (request.Language != null) + { + var culture = _localization.FindLanguageInfo(request.Language); + + if (culture != null) + { + request.TwoLetterISOLanguageName = culture.TwoLetterISOLanguageName; + } + } + var contentType = request.ContentType; var providers = _subtitleProviders .Where(i => i.SupportedMediaTypes.Contains(contentType)) + .Where(i => !request.DisabledSubtitleFetchers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)) + .OrderBy(i => + { + var index = request.SubtitleFetcherOrder.ToList().IndexOf(i.Name); + return index == -1 ? int.MaxValue : index; + }) .ToArray(); // If not searching all, search one at a time until something is found @@ -109,14 +134,22 @@ namespace MediaBrowser.Providers.Subtitles return _config.GetConfiguration<SubtitleOptions>("subtitles"); } + public Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken) + { + var libraryOptions = BaseItem.LibraryManager.GetLibraryOptions(video); + + return DownloadSubtitles(video, libraryOptions, subtitleId, cancellationToken); + } + public async Task DownloadSubtitles(Video video, + LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken) { var parts = subtitleId.Split(new[] { '_' }, 2); var provider = GetProvider(parts.First()); - var saveInMediaFolder = video.IsSaveLocalMetadataEnabled(); + var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; try { @@ -180,6 +213,8 @@ namespace MediaBrowser.Providers.Subtitles try { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(savePath)); + using (var fs = _fileSystem.GetFileStream(savePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { await stream.CopyToAsync(fs).ConfigureAwait(false); @@ -210,8 +245,7 @@ namespace MediaBrowser.Providers.Subtitles public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken) { - if (video.LocationType != LocationType.FileSystem || - video.VideoType != VideoType.VideoFile) + if (video.VideoType != VideoType.VideoFile) { return Task.FromResult<RemoteSubtitleInfo[]>(new RemoteSubtitleInfo[] { }); } @@ -275,12 +309,12 @@ namespace MediaBrowser.Providers.Subtitles return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name))); } - public Task DeleteSubtitles(string itemId, int index) + public Task DeleteSubtitles(BaseItem item, int index) { var stream = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery { Index = index, - ItemId = new Guid(itemId), + ItemId = item.Id, Type = MediaStreamType.Subtitle }).First(); @@ -297,7 +331,7 @@ namespace MediaBrowser.Providers.Subtitles _monitor.ReportFileSystemChangeComplete(path, false); } - return _libraryManager.GetItemById(itemId).RefreshMetadata(CancellationToken.None); + return item.RefreshMetadata(CancellationToken.None); } public Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken) @@ -310,9 +344,8 @@ namespace MediaBrowser.Providers.Subtitles return provider.GetSubtitles(id, cancellationToken); } - public SubtitleProviderInfo[] GetProviders(string itemId) + public SubtitleProviderInfo[] GetSupportedProviders(BaseItem video) { - var video = _libraryManager.GetItemById(itemId) as Video; VideoContentType mediaType; if (video is Episode) diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 771570cbc7..4f528086e0 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -8,8 +8,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; +using System.Collections.Generic; using MediaBrowser.Model.IO; using MediaBrowser.Model.Globalization; @@ -34,9 +33,9 @@ namespace MediaBrowser.Providers.TV _fileSystem = fileSystem; } - public async Task Run(Series series, CancellationToken cancellationToken) + public async Task<bool> Run(Series series, CancellationToken cancellationToken) { - await RemoveObsoleteSeasons(series).ConfigureAwait(false); + var seasonsRemoved = RemoveObsoleteSeasons(series); var hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false); @@ -49,6 +48,8 @@ namespace MediaBrowser.Providers.TV //await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService)) // .ConfigureAwait(false); } + + return seasonsRemoved || hasNewSeasons; } private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken) @@ -60,32 +61,44 @@ namespace MediaBrowser.Providers.TV var hasChanges = false; + List<Season> seasons = null; + // Loop through the unique season numbers foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1) .Where(i => i >= 0) .Distinct() .ToList()) { - var existingSeason = series.Children.OfType<Season>() + if (seasons == null) + { + seasons = series.Children.OfType<Season>().ToList(); + } + var existingSeason = seasons .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); if (existingSeason == null) { await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false); - hasChanges = true; + seasons = null; } else if (existingSeason.IsVirtualItem) { existingSeason.IsVirtualItem = false; existingSeason.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken); + seasons = null; } } // Unknown season - create a dummy season to put these under if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue)) { - var existingSeason = series.Children.OfType<Season>() + if (seasons == null) + { + seasons = series.Children.OfType<Season>().ToList(); + } + + var existingSeason = seasons .FirstOrDefault(i => !i.IndexNumber.HasValue); if (existingSeason == null) @@ -93,11 +106,13 @@ namespace MediaBrowser.Providers.TV await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false); hasChanges = true; + seasons = null; } else if (existingSeason.IsVirtualItem) { existingSeason.IsVirtualItem = false; existingSeason.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken); + seasons = null; } } @@ -137,7 +152,7 @@ namespace MediaBrowser.Providers.TV return season; } - private async Task<bool> RemoveObsoleteSeasons(Series series) + private bool RemoveObsoleteSeasons(Series series) { var existingSeasons = series.Children.OfType<Season>().ToList(); @@ -177,13 +192,13 @@ namespace MediaBrowser.Providers.TV foreach (var seasonToRemove in seasonsToRemove) { - _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber); + _logger.Info("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber); - await seasonToRemove.Delete(new DeleteOptions + _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions { DeleteFileLocation = true - }).ConfigureAwait(false); + }, false); hasChanges = true; } diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 49175f62f9..cea907ca3e 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -35,14 +35,14 @@ namespace MediaBrowser.Providers.TV } var seriesId = item.FindSeriesId(); - if (item.SeriesId != seriesId) + if (!item.SeriesId.Equals(seriesId)) { item.SeriesId = seriesId; updateType |= ItemUpdateType.MetadataImport; } var seasonId = item.FindSeasonId(); - if (item.SeasonId != seasonId) + if (!item.SeasonId.Equals(seasonId)) { item.SeasonId = seasonId; updateType |= ItemUpdateType.MetadataImport; @@ -80,21 +80,6 @@ namespace MediaBrowser.Providers.TV targetItem.AirsBeforeEpisodeNumber = sourceItem.AirsBeforeEpisodeNumber; } - if (replaceData || !targetItem.DvdSeasonNumber.HasValue) - { - targetItem.DvdSeasonNumber = sourceItem.DvdSeasonNumber; - } - - if (replaceData || !targetItem.DvdEpisodeNumber.HasValue) - { - targetItem.DvdEpisodeNumber = sourceItem.DvdEpisodeNumber; - } - - if (replaceData || !targetItem.AbsoluteEpisodeNumber.HasValue) - { - targetItem.AbsoluteEpisodeNumber = sourceItem.AbsoluteEpisodeNumber; - } - if (replaceData || !targetItem.IndexNumberEnd.HasValue) { targetItem.IndexNumberEnd = sourceItem.IndexNumberEnd; diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs index 002c98e957..a89dee1e9b 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.TV { @@ -50,12 +51,12 @@ namespace MediaBrowser.Providers.TV get { return "FanArt"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Season; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); @@ -153,7 +154,6 @@ namespace MediaBrowser.Providers.TV PopulateImages(list, obj.showbackground, ImageType.Backdrop, 1920, 1080, seasonNumber); } - private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartSeriesProvider.Image> images, ImageType type, @@ -175,7 +175,7 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(season) && - int.TryParse(season, NumberStyles.Any, _usCulture, out imageSeasonNumber) && + int.TryParse(season, NumberStyles.Integer, _usCulture, out imageSeasonNumber) && seasonNumber == imageSeasonNumber) { var likesString = i.likes; @@ -188,11 +188,11 @@ namespace MediaBrowser.Providers.TV Width = width, Height = height, ProviderName = Name, - Url = _regex_http.Replace(url, "https://", 1), + Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase), Language = i.lang }; - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out likes)) { info.CommunityRating = likes; } diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs index 33bf1a7c28..3ca0137300 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs @@ -23,6 +23,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.TV { @@ -59,12 +60,12 @@ namespace MediaBrowser.Providers.TV get { return "FanArt"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Series; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -77,7 +78,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); @@ -165,7 +166,6 @@ namespace MediaBrowser.Providers.TV PopulateImages(list, obj.tvposter, ImageType.Primary, 1000, 1426); } - private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<Image> images, ImageType type, @@ -198,11 +198,11 @@ namespace MediaBrowser.Providers.TV Width = width, Height = height, ProviderName = Name, - Url = _regex_http.Replace(url, "https://", 1), + Url = url.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase), Language = i.lang }; - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Integer, _usCulture, out likes)) { info.CommunityRating = likes; } @@ -273,7 +273,7 @@ namespace MediaBrowser.Providers.TV if (fileInfo.Exists) { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { return; } @@ -379,7 +379,7 @@ namespace MediaBrowser.Providers.TV { public IEnumerable<ConfigurationStore> GetConfigurations() { - return new List<ConfigurationStore> + return new ConfigurationStore[] { new ConfigurationStore { diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index b68e6e4a9f..6e469a0411 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -18,6 +18,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Xml; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Providers.TV { @@ -30,8 +31,6 @@ namespace MediaBrowser.Providers.TV private readonly IFileSystem _fileSystem; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private static readonly SemaphoreSlim ResourceLock = new SemaphoreSlim(1, 1); - public static bool IsRunning = false; private readonly IXmlReaderSettingsFactory _xmlSettings; public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings) @@ -44,42 +43,9 @@ namespace MediaBrowser.Providers.TV _xmlSettings = xmlSettings; } - public async Task Run(List<IGrouping<string, Series>> series, bool addNewItems, CancellationToken cancellationToken) + public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken) { - await ResourceLock.WaitAsync(cancellationToken).ConfigureAwait(false); - IsRunning = true; - - foreach (var seriesGroup in series) - { - try - { - await Run(seriesGroup, addNewItems, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - catch (IOException ex) - { - //_logger.Warn("Series files missing for series id {0}", seriesGroup.Key); - _logger.ErrorException("Error in missing episode provider for series id {0}", ex, seriesGroup.Key); - } - catch (Exception ex) - { - _logger.ErrorException("Error in missing episode provider for series id {0}", ex, seriesGroup.Key); - } - } - - IsRunning = false; - ResourceLock.Release(); - } - - private async Task Run(IGrouping<string, Series> group, bool addNewItems, CancellationToken cancellationToken) - { - var seriesList = group.ToList(); - var tvdbId = seriesList - .Select(i => i.GetProviderId(MetadataProviders.Tvdb)) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + var tvdbId = series.GetProviderId(MetadataProviders.Tvdb); // Todo: Support series by imdb id var seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); @@ -90,13 +56,13 @@ namespace MediaBrowser.Providers.TV // Doesn't have required provider id's if (string.IsNullOrWhiteSpace(seriesDataPath)) { - return; + return false; } // Check this in order to avoid logging an exception due to directory not existing if (!_fileSystem.DirectoryExists(seriesDataPath)) { - return; + return false; } var episodeFiles = _fileSystem.GetFilePaths(seriesDataPath) @@ -120,64 +86,61 @@ namespace MediaBrowser.Providers.TV if (int.TryParse(parts[2], NumberStyles.Integer, _usCulture, out episodeNumber)) { - return new Tuple<int, int>(seasonNumber, episodeNumber); + return new ValueTuple<int, int>(seasonNumber, episodeNumber); } } } - return new Tuple<int, int>(-1, -1); + return new ValueTuple<int, int>(-1, -1); }) .Where(i => i.Item1 != -1 && i.Item2 != -1) .ToList(); - var hasBadData = HasInvalidContent(seriesList); + var allRecursiveChildren = series.GetRecursiveChildren(); + + var hasBadData = HasInvalidContent(series, allRecursiveChildren); // Be conservative here to avoid creating missing episodes for ones they already have - var addMissingEpisodes = !hasBadData && seriesList.All(i => _libraryManager.GetLibraryOptions(i).ImportMissingEpisodes); + var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes; - var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(seriesList, episodeLookup) - .ConfigureAwait(false); + var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(series, allRecursiveChildren, episodeLookup); - var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(seriesList, episodeLookup, addMissingEpisodes) - .ConfigureAwait(false); + if (anySeasonsRemoved) + { + // refresh this + allRecursiveChildren = series.GetRecursiveChildren(); + } - var hasNewEpisodes = false; + var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(series, allRecursiveChildren, episodeLookup, addMissingEpisodes); - if (addNewItems && seriesList.All(i => i.IsInternetMetadataEnabled())) + if (anyEpisodesRemoved) { - var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase)); + // refresh this + allRecursiveChildren = series.GetRecursiveChildren(); + } - if (seriesConfig == null || !seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase)) - { - hasNewEpisodes = await AddMissingEpisodes(seriesList, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken) - .ConfigureAwait(false); - } + var hasNewEpisodes = false; + + if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series) ,TvdbSeriesProvider.Current.Name)) + { + hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken) + .ConfigureAwait(false); } if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved) { - foreach (var series in seriesList) - { - var directoryService = new DirectoryService(_logger, _fileSystem); - - await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false); - - await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService), true) - .ConfigureAwait(false); - } + return true; } + + return false; } /// <summary> /// Returns true if a series has any seasons or episodes without season or episode numbers /// If this data is missing no virtual items will be added in order to prevent possible duplicates /// </summary> - /// <param name="group"></param> - /// <returns></returns> - private bool HasInvalidContent(IEnumerable<Series> group) + private bool HasInvalidContent(Series series, IList<BaseItem> allItems) { - var allItems = group.ToList().SelectMany(i => i.GetRecursiveChildren()).ToList(); - return allItems.OfType<Season>().Any(i => !i.IndexNumber.HasValue) || allItems.OfType<Episode>().Any(i => { @@ -198,18 +161,17 @@ namespace MediaBrowser.Providers.TV /// </summary> /// <param name="series">The series.</param> /// <returns>Task.</returns> - private async Task<bool> AddMissingEpisodes(List<Series> series, + private async Task<bool> AddMissingEpisodes(Series series, + IList<BaseItem> allItems, bool addMissingEpisodes, string seriesDataPath, - IEnumerable<Tuple<int, int>> episodeLookup, + IEnumerable<ValueTuple<int, int>> episodeLookup, CancellationToken cancellationToken) { - var existingEpisodes = (from s in series - from c in s.GetRecursiveChildren(i => i is Episode).Cast<Episode>() - select new Tuple<int, Episode>((c.ParentIndexNumber ?? 0), c)) + var existingEpisodes = allItems.OfType<Episode>() .ToList(); - var lookup = episodeLookup as IList<Tuple<int, int>> ?? episodeLookup.ToList(); + var lookup = episodeLookup as IList<ValueTuple<int, int>> ?? episodeLookup.ToList(); var seasonCounts = (from e in lookup group e by e.Item1 into g @@ -248,8 +210,6 @@ namespace MediaBrowser.Providers.TV var now = DateTime.UtcNow; - var targetSeries = DetermineAppropriateSeries(series, tuple.Item1); - now = now.AddDays(0 - UnairedEpisodeThresholdDays); if (airDate.Value < now) @@ -257,8 +217,8 @@ namespace MediaBrowser.Providers.TV if (addMissingEpisodes) { // tvdb has a lot of nearly blank episodes - _logger.Info("Creating virtual missing episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + _logger.Info("Creating virtual missing episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2); + await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -266,8 +226,8 @@ namespace MediaBrowser.Providers.TV else if (airDate.Value > now) { // tvdb has a lot of nearly blank episodes - _logger.Info("Creating virtual unaired episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + _logger.Info("Creating virtual unaired episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2); + await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -276,50 +236,37 @@ namespace MediaBrowser.Providers.TV return hasChanges; } - private Series DetermineAppropriateSeries(List<Series> series, int seasonNumber) - { - if (series.Count == 1) - { - return series[0]; - } - - return series.FirstOrDefault(s => s.GetRecursiveChildren(i => i is Season).Any(season => (season.IndexNumber) == seasonNumber)) ?? - series.FirstOrDefault(s => s.GetRecursiveChildren(i => i is Season).Any(season => (season.IndexNumber) == 1)) ?? - series.OrderBy(s => s.GetRecursiveChildren(i => i is Season).Select(season => season.IndexNumber).Min()).First(); - } - /// <summary> /// Removes the virtual entry after a corresponding physical version has been added /// </summary> - private async Task<bool> RemoveObsoleteOrMissingEpisodes(IEnumerable<Series> series, - IEnumerable<Tuple<int, int>> episodeLookup, + private bool RemoveObsoleteOrMissingEpisodes(Series series, + IList<BaseItem> allRecursiveChildren, + IEnumerable<ValueTuple<int, int>> episodeLookup, bool allowMissingEpisodes) { - var existingEpisodes = (from s in series - from c in s.GetRecursiveChildren(i => i is Episode).Cast<Episode>() - select new { Episode = c }) + var existingEpisodes = allRecursiveChildren.OfType<Episode>() .ToList(); var physicalEpisodes = existingEpisodes - .Where(i => i.Episode.LocationType != LocationType.Virtual) + .Where(i => i.LocationType != LocationType.Virtual) .ToList(); var virtualEpisodes = existingEpisodes - .Where(i => i.Episode.LocationType == LocationType.Virtual) + .Where(i => i.LocationType == LocationType.Virtual) .ToList(); var episodesToRemove = virtualEpisodes .Where(i => { - if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue) + if (i.IndexNumber.HasValue && i.ParentIndexNumber.HasValue) { - var seasonNumber = i.Episode.ParentIndexNumber.Value; - var episodeNumber = i.Episode.IndexNumber.Value; + var seasonNumber = i.ParentIndexNumber.Value; + var episodeNumber = i.IndexNumber.Value; // If there's a physical episode with the same season and episode number, delete it if (physicalEpisodes.Any(p => - p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value) == seasonNumber && - p.Episode.ContainsEpisodeNumber(episodeNumber))) + p.ParentIndexNumber.HasValue && (p.ParentIndexNumber.Value) == seasonNumber && + p.ContainsEpisodeNumber(episodeNumber))) { return true; } @@ -330,10 +277,10 @@ namespace MediaBrowser.Providers.TV return true; } - if (!allowMissingEpisodes && i.Episode.IsMissingEpisode) + if (!allowMissingEpisodes && i.IsMissingEpisode) { // If it's missing, but not unaired, remove it - if (!i.Episode.PremiereDate.HasValue || i.Episode.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < DateTime.Now.Date) + if (!i.PremiereDate.HasValue || i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < DateTime.Now.Date) { return true; } @@ -348,13 +295,13 @@ namespace MediaBrowser.Providers.TV var hasChanges = false; - foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode)) + foreach (var episodeToRemove in episodesToRemove) { - await episodeToRemove.Delete(new DeleteOptions + _libraryManager.DeleteItem(episodeToRemove, new DeleteOptions { DeleteFileLocation = true - }).ConfigureAwait(false); + }, false); hasChanges = true; } @@ -368,13 +315,11 @@ namespace MediaBrowser.Providers.TV /// <param name="series">The series.</param> /// <param name="episodeLookup">The episode lookup.</param> /// <returns>Task{System.Boolean}.</returns> - private async Task<bool> RemoveObsoleteOrMissingSeasons(IEnumerable<Series> series, - IEnumerable<Tuple<int, int>> episodeLookup) + private bool RemoveObsoleteOrMissingSeasons(Series series, + IList<BaseItem> allRecursiveChildren, + IEnumerable<ValueTuple<int, int>> episodeLookup) { - var existingSeasons = (from s in series - from c in s.Children.OfType<Season>() - select c) - .ToList(); + var existingSeasons = allRecursiveChildren.OfType<Season>().ToList(); var physicalSeasons = existingSeasons .Where(i => i.LocationType != LocationType.Virtual) @@ -384,6 +329,8 @@ namespace MediaBrowser.Providers.TV .Where(i => i.LocationType == LocationType.Virtual) .ToList(); + var allEpisodes = allRecursiveChildren.OfType<Episode>().ToList(); + var seasonsToRemove = virtualSeasons .Where(i => { @@ -397,10 +344,13 @@ namespace MediaBrowser.Providers.TV return true; } - // If the season no longer exists in the remote lookup, delete it + // If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it if (episodeLookup.All(e => e.Item1 != seasonNumber)) { - return true; + if (allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder)) + { + return true; + } } return false; @@ -408,7 +358,7 @@ namespace MediaBrowser.Providers.TV // Season does not have a number // Remove if there are no episodes directly in series without a season number - return i.Series.GetRecursiveChildren(e => e is Episode).Cast<Episode>().All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); + return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); }) .ToList(); @@ -416,11 +366,11 @@ namespace MediaBrowser.Providers.TV foreach (var seasonToRemove in seasonsToRemove) { - await seasonToRemove.Delete(new DeleteOptions + _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions { DeleteFileLocation = true - }).ConfigureAwait(false); + }, false); hasChanges = true; } @@ -456,7 +406,7 @@ namespace MediaBrowser.Providers.TV ParentIndexNumber = seasonNumber, Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)), IsVirtualItem = true, - SeasonId = season == null ? (Guid?)null : season.Id, + SeasonId = season == null ? Guid.Empty : season.Id, SeriesId = series.Id }; @@ -474,7 +424,7 @@ namespace MediaBrowser.Providers.TV /// <param name="seasonCounts"></param> /// <param name="tuple">The tuple.</param> /// <returns>Episode.</returns> - private Episode GetExistingEpisode(IList<Tuple<int, Episode>> existingEpisodes, Dictionary<int, int> seasonCounts, Tuple<int, int> tuple) + private Episode GetExistingEpisode(IList<Episode> existingEpisodes, Dictionary<int, int> seasonCounts, ValueTuple<int, int> tuple) { var s = tuple.Item1; var e = tuple.Item2; @@ -496,12 +446,10 @@ namespace MediaBrowser.Providers.TV return null; } - private static Episode GetExistingEpisode(IEnumerable<Tuple<int, Episode>> existingEpisodes, int season, int episode) + private Episode GetExistingEpisode(IEnumerable<Episode> existingEpisodes, int season, int episode) { return existingEpisodes - .Where(i => i.Item1 == season && i.Item2.ContainsEpisodeNumber(episode)) - .Select(i => i.Item2) - .FirstOrDefault(); + .FirstOrDefault(i => i.ParentIndexNumber == season && i.ContainsEpisodeNumber(episode)); } /// <summary> diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs index 48af89830c..e65c36d981 100644 --- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs @@ -12,8 +12,7 @@ using MediaBrowser.Providers.Omdb; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; +using MediaBrowser.Common; namespace MediaBrowser.Providers.TV { @@ -26,14 +25,16 @@ namespace MediaBrowser.Providers.TV private readonly OmdbItemProvider _itemProvider; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; - public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; _fileSystem = fileSystem; _configurationManager = configurationManager; - _itemProvider = new OmdbItemProvider(jsonSerializer, httpClient, logger, libraryManager, fileSystem, configurationManager); + _appHost = appHost; + _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, logger, libraryManager, fileSystem, configurationManager); } public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) @@ -60,7 +61,7 @@ namespace MediaBrowser.Providers.TV { if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue) { - result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager) + result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager) .FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProviders.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index 5e3dc3c627..a9a026db9d 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -30,13 +30,6 @@ namespace MediaBrowser.Providers.TV } } - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) - { - var episodes = item.GetEpisodes(); - updateType |= SavePremiereDate(item, episodes); - updateType |= SaveIsVirtualItem(item, episodes); - } - var seriesName = item.FindSeriesName(); if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) { @@ -52,7 +45,7 @@ namespace MediaBrowser.Providers.TV } var seriesId = item.FindSeriesId(); - if (item.SeriesId != seriesId) + if (!item.SeriesId.Equals(seriesId)) { item.SeriesId = seriesId; updateType |= ItemUpdateType.MetadataImport; @@ -61,32 +54,37 @@ namespace MediaBrowser.Providers.TV return updateType; } - protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override bool EnableUpdatingPremiereDateFromChildren { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + get + { + return true; + } } - private ItemUpdateType SavePremiereDate(Season item, List<BaseItem> episodes) + protected override IList<BaseItem> GetChildrenForMetadataUpdates(Season item) { - var dates = episodes.Where(i => i.PremiereDate.HasValue).Select(i => i.PremiereDate.Value).ToList(); + return item.GetEpisodes(); + } - DateTime? premiereDate = null; + protected override ItemUpdateType UpdateMetadataFromChildren(Season item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); - if (dates.Count > 0) + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { - premiereDate = dates.Min(); + updateType |= SaveIsVirtualItem(item, children); } - if (item.PremiereDate != premiereDate) - { - item.PremiereDate = premiereDate; - return ItemUpdateType.MetadataEdit; - } + return updateType; + } - return ItemUpdateType.None; + protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - private ItemUpdateType SaveIsVirtualItem(Season item, List<BaseItem> episodes) + private ItemUpdateType SaveIsVirtualItem(Season item, IList<BaseItem> episodes) { var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual)); diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 3977ba9d9a..338e7b4ed6 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -9,38 +9,44 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Xml; namespace MediaBrowser.Providers.TV { public class SeriesMetadataService : MetadataService<Series, SeriesInfo> { private readonly ILocalizationManager _localization; + private readonly IXmlReaderSettingsFactory _xmlSettings; - public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization, IXmlReaderSettingsFactory xmlSettings) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { _localization = localization; + _xmlSettings = xmlSettings; } protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false); - if (refreshOptions.IsPostRecursiveRefresh) - { - var provider = new DummySeasonProvider(ServerConfigurationManager, Logger, _localization, LibraryManager, FileSystem); + var seasonProvider = new DummySeasonProvider(ServerConfigurationManager, Logger, _localization, LibraryManager, FileSystem); + await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false); + + var provider = new MissingEpisodeProvider(Logger, + ServerConfigurationManager, + LibraryManager, + _localization, + FileSystem, + _xmlSettings); - try - { - await provider.Run(item, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error in DummySeasonProvider", ex); - } + try + { + await provider.Run(item, true, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error in DummySeasonProvider", ex); } } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs index 728bbc505a..f6568510d4 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.TV : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logManager) {} - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var episode = (Controller.Entities.TV.Episode)item; var series = episode.Series; @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.TV var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); list.AddRange(GetPosters(response.images).Select(i => new RemoteImageInfo { @@ -124,7 +124,7 @@ namespace MediaBrowser.Providers.TV get { return "TheMovieDb"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Controller.Entities.TV.Episode; } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs index b199364807..4d7cec79fc 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.TV { public abstract class MovieDbProviderBase { - private const string EpisodeUrlPattern = @"https://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; + private const string EpisodeUrlPattern = MovieDbProvider.BaseMovieDbUrl + @"3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _configurationManager; private readonly IJsonSerializer _jsonSerializer; @@ -70,9 +70,9 @@ namespace MediaBrowser.Providers.TV if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return Task.FromResult(true); + return Task.CompletedTask; } } @@ -135,7 +135,7 @@ namespace MediaBrowser.Providers.TV { using (var json = response.Content) { - return _jsonSerializer.DeserializeFromStream<RootObject>(json); + return await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs index 0c4c2d9ab5..63484483f2 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.TV { public class MovieDbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo> { - private const string GetTvInfo3 = @"https://api.themoviedb.org/3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos"; + private const string GetTvInfo3 = MovieDbProvider.BaseMovieDbUrl + @"3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos"; private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _configurationManager; private readonly IJsonSerializer _jsonSerializer; @@ -141,7 +141,6 @@ namespace MediaBrowser.Providers.TV return _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath); } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(tmdbId)) @@ -160,9 +159,9 @@ namespace MediaBrowser.Providers.TV if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } @@ -224,7 +223,7 @@ namespace MediaBrowser.Providers.TV { using (var json = response.Content) { - return _jsonSerializer.DeserializeFromStream<RootObject>(json); + return await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs index f05f7bb309..6124e7a9cb 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs @@ -40,12 +40,12 @@ namespace MediaBrowser.Providers.TV get { return "TheMovieDb"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Series; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -54,11 +54,11 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); - var results = await FetchImages((BaseItem)item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false); + var results = await FetchImages(item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false); if (results == null) { @@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.TV var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var language = item.GetPreferredMetadataLanguage(); diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs index 08099179ce..6e9b9b239a 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.TV { public class MovieDbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder { - private const string GetTvInfo3 = @"https://api.themoviedb.org/3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; + private const string GetTvInfo3 = MovieDbProvider.BaseMovieDbUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); internal static MovieDbSeriesProvider Current { get; private set; } @@ -72,7 +72,7 @@ namespace MediaBrowser.Providers.TV var obj = _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath); var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var remoteResult = new RemoteSearchResult { @@ -208,13 +208,17 @@ namespace MediaBrowser.Providers.TV result.Item = new Series(); result.ResultLanguage = seriesInfo.ResultLanguage; - ProcessMainInfo(result.Item, seriesInfo, preferredCountryCode); + var settings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); + + ProcessMainInfo(result, seriesInfo, preferredCountryCode, settings); return result; } - private void ProcessMainInfo(Series series, RootObject seriesInfo, string preferredCountryCode) + private void ProcessMainInfo(MetadataResult<Series> seriesResult, RootObject seriesInfo, string preferredCountryCode, TmdbSettingsResult settings) { + var series = seriesResult.Item; + series.Name = seriesInfo.name; series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.id.ToString(_usCulture)); @@ -237,10 +241,10 @@ namespace MediaBrowser.Providers.TV if (seriesInfo.genres != null) { - series.Genres = seriesInfo.genres.Select(i => i.name).ToList(); + series.Genres = seriesInfo.genres.Select(i => i.name).ToArray(); } - series.HomePageUrl = seriesInfo.homepage; + //series.HomePageUrl = seriesInfo.homepage; series.RunTimeTicks = seriesInfo.episode_run_time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); @@ -307,6 +311,35 @@ namespace MediaBrowser.Providers.TV } } } + + seriesResult.ResetPeople(); + var tmdbImageUrl = settings.images.GetImageUrl("original"); + + if (seriesInfo.credits != null && seriesInfo.credits.cast != null) + { + foreach (var actor in seriesInfo.credits.cast.OrderBy(a => a.order)) + { + var personInfo = new PersonInfo + { + Name = actor.name.Trim(), + Role = actor.character, + Type = PersonType.Actor, + SortOrder = actor.order + }; + + if (!string.IsNullOrWhiteSpace(actor.profile_path)) + { + personInfo.ImageUrl = tmdbImageUrl + actor.profile_path; + } + + if (actor.id > 0) + { + personInfo.SetProviderId(MetadataProviders.Tmdb, actor.id.ToString(CultureInfo.InvariantCulture)); + } + + seriesResult.AddPerson(personInfo); + } + } } internal static string GetSeriesDataPath(IApplicationPaths appPaths, string tmdbId) @@ -362,7 +395,7 @@ namespace MediaBrowser.Providers.TV { using (var json = response.Content) { - mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); + mainResult = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); if (!string.IsNullOrEmpty(language)) { @@ -399,7 +432,7 @@ namespace MediaBrowser.Providers.TV { using (var json = response.Content) { - var englishResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); + var englishResult = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(json).ConfigureAwait(false); mainResult.overview = englishResult.overview; mainResult.ResultLanguage = "en"; @@ -410,7 +443,6 @@ namespace MediaBrowser.Providers.TV return mainResult; } - private readonly Task _cachedTask = Task.FromResult(true); internal Task EnsureSeriesInfo(string tmdbId, string language, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(tmdbId)) @@ -425,9 +457,9 @@ namespace MediaBrowser.Providers.TV if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) { - return _cachedTask; + return Task.CompletedTask; } } @@ -450,7 +482,7 @@ namespace MediaBrowser.Providers.TV private async Task<RemoteSearchResult> FindByExternalId(string id, string externalSource, CancellationToken cancellationToken) { - var url = string.Format("https://api.themoviedb.org/3/find/{0}?api_key={1}&external_source={2}", + var url = string.Format(MovieDbProvider.BaseMovieDbUrl + @"3/find/{0}?api_key={1}&external_source={2}", id, MovieDbProvider.ApiKey, externalSource); @@ -465,7 +497,7 @@ namespace MediaBrowser.Providers.TV { using (var json = response.Content) { - var result = _jsonSerializer.DeserializeFromStream<MovieDbSearch.ExternalIdLookupResult>(json); + var result = await _jsonSerializer.DeserializeFromStreamAsync<MovieDbSearch.ExternalIdLookupResult>(json).ConfigureAwait(false); if (result != null && result.tv_results != null) { @@ -474,7 +506,7 @@ namespace MediaBrowser.Providers.TV if (tv != null) { var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); var remoteResult = new RemoteSearchResult { @@ -656,8 +688,8 @@ namespace MediaBrowser.Providers.TV { get { - // After Omdb and Tvdb - return 2; + // After Tvdb + return 1; } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs index 7b6bcc4c7c..23fefa4840 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs @@ -35,12 +35,12 @@ namespace MediaBrowser.Providers.TV get { return "TheTVDB"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Episode; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.TV }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var episode = (Episode)item; var series = episode.Series; diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index d823de7863..41155aca16 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -62,19 +62,9 @@ namespace MediaBrowser.Providers.TV { var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds); - var searchNumbers = new EpisodeNumbers(); - - if (searchInfo.IndexNumber.HasValue) - { - searchNumbers.EpisodeNumber = searchInfo.IndexNumber.Value; - } - - searchNumbers.SeasonNumber = searchInfo.ParentIndexNumber; - searchNumbers.EpisodeNumberEnd = searchInfo.IndexNumberEnd ?? searchNumbers.EpisodeNumber; - try { - var metadataResult = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken); + var metadataResult = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken); if (metadataResult.HasMetadata) { @@ -119,21 +109,16 @@ namespace MediaBrowser.Providers.TV if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue)) { - await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); - - var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); - var searchNumbers = new EpisodeNumbers(); - if (searchInfo.IndexNumber.HasValue) + if (string.IsNullOrEmpty(seriesDataPath)) { - searchNumbers.EpisodeNumber = searchInfo.IndexNumber.Value; + return result; } - searchNumbers.SeasonNumber = searchInfo.ParentIndexNumber; - searchNumbers.EpisodeNumberEnd = searchInfo.IndexNumberEnd ?? searchNumbers.EpisodeNumber; try { - result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken); + result = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken); } catch (FileNotFoundException) { @@ -176,13 +161,6 @@ namespace MediaBrowser.Providers.TV } } - private class EpisodeNumbers - { - public int EpisodeNumber; - public int? SeasonNumber; - public int EpisodeNumberEnd; - } - /// <summary> /// Fetches the episode data. /// </summary> @@ -191,7 +169,7 @@ namespace MediaBrowser.Providers.TV /// <param name="seriesDataPath">The series data path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> - private MetadataResult<Episode> FetchEpisodeData(EpisodeInfo id, EpisodeNumbers searchNumbers, string seriesDataPath, CancellationToken cancellationToken) + private MetadataResult<Episode> FetchEpisodeData(EpisodeInfo id, string seriesDataPath, CancellationToken cancellationToken) { var result = new MetadataResult<Episode>() { @@ -207,7 +185,7 @@ namespace MediaBrowser.Providers.TV if (xmlNodes.Count > 0) { - FetchMainEpisodeInfo(result, xmlNodes[0], cancellationToken); + FetchMainEpisodeInfo(result, xmlNodes[0], id.SeriesDisplayOrder, cancellationToken); result.HasMetadata = true; } @@ -226,7 +204,7 @@ namespace MediaBrowser.Providers.TV if (searchInfo.IndexNumber.HasValue) { - var files = GetEpisodeXmlFiles(searchInfo.ParentIndexNumber, searchInfo.IndexNumber, searchInfo.IndexNumberEnd, _fileSystem.GetDirectoryName(xmlFile)); + var files = GetEpisodeXmlFiles(searchInfo.SeriesDisplayOrder, searchInfo.ParentIndexNumber, searchInfo.IndexNumber, searchInfo.IndexNumberEnd, _fileSystem.GetDirectoryName(xmlFile)); list = files.Select(GetXmlReader).ToList(); } @@ -239,7 +217,48 @@ namespace MediaBrowser.Providers.TV return list; } - private List<FileSystemMetadata> GetEpisodeXmlFiles(int? seasonNumber, int? episodeNumber, int? endingEpisodeNumber, string seriesDataPath) + private string GetEpisodeFileName(string seriesDisplayOrder, int? seasonNumber, int? episodeNumber) + { + if (string.Equals(seriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase)) + { + return string.Format("episode-abs-{0}.xml", episodeNumber); + } + else if (string.Equals(seriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase)) + { + return string.Format("episode-dvd-{0}-{1}.xml", seasonNumber.Value, episodeNumber); + } + else + { + return string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber); + } + } + + private FileSystemMetadata GetEpisodeFileInfoWithFallback(string seriesDataPath, string seriesDisplayOrder, int? seasonNumber, int? episodeNumber) + { + var file = Path.Combine(seriesDataPath, GetEpisodeFileName(seriesDisplayOrder, seasonNumber, episodeNumber)); + var fileInfo = _fileSystem.GetFileInfo(file); + + if (fileInfo.Exists) + { + return fileInfo; + } + + if (!seasonNumber.HasValue) + { + return fileInfo; + } + + // revert to aired order + if (string.Equals(seriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase) || string.Equals(seriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase)) + { + file = Path.Combine(seriesDataPath, GetEpisodeFileName(null, seasonNumber, episodeNumber)); + return _fileSystem.GetFileInfo(file); + } + + return fileInfo; + } + + private List<FileSystemMetadata> GetEpisodeXmlFiles(string seriesDisplayOrder, int? seasonNumber, int? episodeNumber, int? endingEpisodeNumber, string seriesDataPath) { var files = new List<FileSystemMetadata>(); @@ -248,27 +267,16 @@ namespace MediaBrowser.Providers.TV return files; } - var usingAbsoluteData = false; - - if (seasonNumber.HasValue) + if (!seasonNumber.HasValue) { - var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); - var fileInfo = _fileSystem.GetFileInfo(file); - - if (fileInfo.Exists) - { - files.Add(fileInfo); - } + seriesDisplayOrder = "absolute"; } - else + + var fileInfo = GetEpisodeFileInfoWithFallback(seriesDataPath, seriesDisplayOrder, seasonNumber, episodeNumber); + + if (fileInfo.Exists) { - usingAbsoluteData = true; - var file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); - var fileInfo = _fileSystem.GetFileInfo(file); - if (fileInfo.Exists) - { - files.Add(fileInfo); - } + files.Add(fileInfo); } var end = endingEpisodeNumber ?? episodeNumber; @@ -276,18 +284,8 @@ namespace MediaBrowser.Providers.TV while (episodeNumber <= end) { - string file; + fileInfo = GetEpisodeFileInfoWithFallback(seriesDataPath, seriesDisplayOrder, seasonNumber, episodeNumber); - if (usingAbsoluteData) - { - file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); - } - else - { - file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); - } - - var fileInfo = _fileSystem.GetFileInfo(file); if (fileInfo.Exists) { files.Add(fileInfo); @@ -439,10 +437,15 @@ namespace MediaBrowser.Providers.TV private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private void FetchMainEpisodeInfo(MetadataResult<Episode> result, XmlReader reader, CancellationToken cancellationToken) + private void FetchMainEpisodeInfo(MetadataResult<Episode> result, XmlReader reader, string seriesOrder, CancellationToken cancellationToken) { var item = result.Item; + int? episodeNumber = null; + int? seasonNumber = null; + int? combinedEpisodeNumber = null; + int? combinedSeasonNumber = null; + // Use XmlReader for best performance using (reader) { @@ -480,94 +483,70 @@ namespace MediaBrowser.Providers.TV break; } - case "DVD_episodenumber": + case "EpisodeNumber": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { - float num; + int rval; - if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { - item.DvdEpisodeNumber = num; + episodeNumber = rval; } } break; } - case "DVD_season": + case "SeasonNumber": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { - float num; + int rval; - if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { - item.DvdSeasonNumber = Convert.ToInt32(num); + seasonNumber = rval; } } break; } - case "EpisodeNumber": + case "Combined_episodenumber": { var val = reader.ReadElementContentAsString(); - if (!item.IndexNumber.HasValue) + if (!string.IsNullOrWhiteSpace(val)) { - if (!string.IsNullOrWhiteSpace(val)) - { - int rval; - - // int.TryParse is local aware, so it can be probamatic, force us culture - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) - { - item.IndexNumber = rval; - } - } - } - - break; - } - - case "SeasonNumber": - { - var val = reader.ReadElementContentAsString(); + float num; - if (!item.ParentIndexNumber.HasValue) - { - if (!string.IsNullOrWhiteSpace(val)) + if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) { - int rval; - - // int.TryParse is local aware, so it can be probamatic, force us culture - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) - { - item.ParentIndexNumber = rval; - } + combinedEpisodeNumber = Convert.ToInt32(num); } } break; } - case "absolute_number": + case "Combined_season": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { - int rval; + float num; - // int.TryParse is local aware, so it can be probamatic, force us culture - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) { - item.AbsoluteEpisodeNumber = rval; + combinedSeasonNumber = Convert.ToInt32(num); } } @@ -771,6 +750,22 @@ namespace MediaBrowser.Providers.TV } } } + + if (string.Equals(seriesOrder, "dvd", StringComparison.OrdinalIgnoreCase)) + { + episodeNumber = combinedEpisodeNumber ?? episodeNumber; + seasonNumber = combinedSeasonNumber ?? seasonNumber; + } + + if (episodeNumber.HasValue) + { + item.IndexNumber = episodeNumber; + } + + if (seasonNumber.HasValue) + { + item.ParentIndexNumber = seasonNumber; + } } private void AddPeople<T>(MetadataResult<T> result, string val, string personType) @@ -787,30 +782,32 @@ namespace MediaBrowser.Providers.TV private void AddGuestStars<T>(MetadataResult<T> result, string val) where T : BaseItem { - // Sometimes tvdb actors have leading spaces - //Regex Info: - //The first block are the posible delimitators (open-parentheses should be there cause if dont the next block will fail) - //The second block Allow the delimitators to be part of the text if they're inside parentheses - var persons = Regex.Matches(val, @"(?<delimitators>([^|,(])|(?<ignoreinParentheses>\([^)]*\)*))+") - .Cast<Match>() - .Select(m => m.Value) - .Where(i => !string.IsNullOrWhiteSpace(i) && !string.IsNullOrEmpty(i)); - - foreach (var person in persons.Select(str => - { - var nameGroup = str.Split(new[] { '(' }, 2, StringSplitOptions.RemoveEmptyEntries); - var name = nameGroup[0].Trim(); - var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null; - if (roles != null) - roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles; - - return new PersonInfo { Type = PersonType.GuestStar, Name = name, Role = roles }; - })) - { - if (!string.IsNullOrWhiteSpace(person.Name)) + // example: + // <GuestStars>|Mark C. Thomas| Dennis Kiefer| David Nelson (David)| Angela Nicholas| Tzi Ma| Kevin P. Kearns (Pasco)|</GuestStars> + var persons = val.Split('|') + .Select(i => i.Trim()) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + foreach (var person in persons) + { + var index = person.IndexOf('('); + string role = null; + var name = person; + + if (index != -1) { - result.AddPerson(person); + role = person.Substring(index + 1).Trim().TrimEnd(')'); + + name = person.Substring(0, index).Trim(); } + + result.AddPerson(new PersonInfo + { + Type = PersonType.GuestStar, + Name = name, + Role = role + }); } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index ebcd61a6e7..2839cb434f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -27,15 +27,17 @@ namespace MediaBrowser.Providers.TV /// </summary> public class TvdbPrescanTask : ILibraryPostScanTask { + public const string TvdbBaseUrl = "https://thetvdb.com/"; + /// <summary> /// The server time URL /// </summary> - private const string ServerTimeUrl = "https://thetvdb.com/api/Updates.php?type=none"; + private const string ServerTimeUrl = TvdbBaseUrl + "api/Updates.php?type=none"; /// <summary> /// The updates URL /// </summary> - private const string UpdatesUrl = "https://thetvdb.com/api/Updates.php?type=all&time={0}"; + private const string UpdatesUrl = TvdbBaseUrl + "api/Updates.php?type=all&time={0}"; /// <summary> /// The _HTTP client @@ -79,14 +81,6 @@ namespace MediaBrowser.Providers.TV /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase)); - - if (seriesConfig != null && seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase)) - { - progress.Report(100); - return; - } - var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); _fileSystem.CreateDirectory(path); @@ -131,7 +125,7 @@ namespace MediaBrowser.Providers.TV var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase) .ToList(); - var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsInternetMetadataEnabled(); + var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(seriesList[0]), TvdbSeriesProvider.Current.Name); if (!enableInternetProviders) { progress.Report(100); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index 22dab1cd7c..319ece156a 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.TV get { return "TheTVDB"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Season; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var season = (Season)item; var series = season.Series; @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.TV var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(seriesDataPath)) + if (!string.IsNullOrEmpty(seriesDataPath)) { var path = Path.Combine(seriesDataPath, "banners.xml"); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index 6f3d763e24..e76ac6cd1c 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -49,12 +49,12 @@ namespace MediaBrowser.Providers.TV get { return "TheTVDB"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Series; } - public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item) + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return new List<ImageType> { @@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasMetadata item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { if (TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) { @@ -72,6 +72,11 @@ namespace MediaBrowser.Providers.TV var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false); + if (string.IsNullOrEmpty(seriesDataPath)) + { + return new RemoteImageInfo[] { }; + } + var path = Path.Combine(seriesDataPath, "banners.xml"); try diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 846ab9558a..8b7efc28b8 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -36,10 +36,9 @@ namespace MediaBrowser.Providers.TV private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly ILocalizationManager _localizationManager; - public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamFactory memoryStreamProvider, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager) + public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager) { _zipClient = zipClient; _httpClient = httpClient; @@ -47,15 +46,17 @@ namespace MediaBrowser.Providers.TV _config = config; _logger = logger; _libraryManager = libraryManager; - _memoryStreamProvider = memoryStreamProvider; _xmlSettings = xmlSettings; _localizationManager = localizationManager; Current = this; } - private const string SeriesSearchUrl = "https://www.thetvdb.com/api/GetSeries.php?seriesname={0}&language={1}"; - private const string SeriesGetZip = "https://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip"; - private const string GetSeriesByImdbId = "https://www.thetvdb.com/api/GetSeriesByRemoteID.php?imdbid={0}&language={1}"; + public const string TvdbBaseUrl = "https://www.thetvdb.com/"; + + private const string SeriesSearchUrl = TvdbBaseUrl + "api/GetSeries.php?seriesname={0}&language={1}"; + private const string SeriesGetZip = TvdbBaseUrl + "api/{0}/series/{1}/all/{2}.zip"; + private const string GetSeriesByImdbId = TvdbBaseUrl + "api/GetSeriesByRemoteID.php?imdbid={0}&language={1}"; + private const string GetSeriesByZap2ItId = TvdbBaseUrl + "api/GetSeriesByRemoteID.php?zap2it={0}&language={1}"; private string NormalizeLanguage(string language) { @@ -108,7 +109,12 @@ namespace MediaBrowser.Providers.TV if (IsValidSeries(itemId.ProviderIds)) { - await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var seriesDataPath = await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); + + if (string.IsNullOrEmpty(seriesDataPath)) + { + return result; + } result.Item = new Series(); result.HasMetadata = true; @@ -142,6 +148,11 @@ namespace MediaBrowser.Providers.TV series.SetProviderId(MetadataProviders.Imdb, id); } + if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id) && !string.IsNullOrEmpty(id)) + { + series.SetProviderId(MetadataProviders.Zap2It, id); + } + var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds); var seriesXmlPath = GetSeriesXmlPath(seriesProviderIds, metadataLanguage); @@ -230,7 +241,7 @@ namespace MediaBrowser.Providers.TV DeleteXmlFiles(seriesDataPath); // Copy to memory stream because we need a seekable stream - using (var ms = _memoryStreamProvider.CreateNew()) + using (var ms = new MemoryStream()) { await zipStream.CopyToAsync(ms).ConfigureAwait(false); @@ -261,7 +272,15 @@ namespace MediaBrowser.Providers.TV private async Task<string> GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken) { - var url = string.Format(GetSeriesByImdbId, id, NormalizeLanguage(language)); + String url; + if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase)) + { + url = string.Format(GetSeriesByZap2ItId, id, NormalizeLanguage(language)); + } + else + { + url = string.Format(GetSeriesByImdbId, id, NormalizeLanguage(language)); + } using (var response = await _httpClient.SendAsync(new HttpRequestOptions { @@ -388,6 +407,15 @@ namespace MediaBrowser.Providers.TV return true; } } + + if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id)) + { + // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet. + if (!string.IsNullOrWhiteSpace(id)) + { + return true; + } + } return false; } @@ -421,7 +449,37 @@ namespace MediaBrowser.Providers.TV // The post-scan task will take care of updates so we don't need to re-download here if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) { - await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + try + { + await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + } + catch (ArgumentNullException) + { + // Unable to determine tvdb id based on imdb id + return null; + } + } + + return seriesDataPath; + } + + if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out seriesId) && !string.IsNullOrWhiteSpace(seriesId)) + { + var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds); + + // Only download if not already there + // The post-scan task will take care of updates so we don't need to re-download here + if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) + { + try + { + await DownloadSeriesZip(seriesId, MetadataProviders.Zap2It.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + } + catch (ArgumentNullException) + { + // Unable to determine tvdb id based on Zap2It id + return null; + } } return seriesDataPath; @@ -516,10 +574,11 @@ namespace MediaBrowser.Providers.TV private async Task<List<RemoteSearchResult>> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) { var url = string.Format(SeriesSearchUrl, WebUtility.UrlEncode(name), NormalizeLanguage(language)); - var searchResults = new List<RemoteSearchResult>(); var comparableName = GetComparableName(name); + var list = new List<Tuple<List<string>, RemoteSearchResult>>(); + using (var response = await _httpClient.SendAsync(new HttpRequestOptions { Url = url, @@ -562,11 +621,11 @@ namespace MediaBrowser.Providers.TV } using (var subtree = reader.ReadSubtree()) { - var searchResult = GetSeriesSearchResultFromSubTree(subtree, comparableName); - if (searchResult != null) + var searchResultInfo = GetSeriesSearchResultFromSubTree(subtree); + if (searchResultInfo != null) { - searchResult.SearchProviderName = Name; - searchResults.Add(searchResult); + searchResultInfo.Item2.SearchProviderName = Name; + list.Add(searchResultInfo); } } break; @@ -587,22 +646,21 @@ namespace MediaBrowser.Providers.TV } } - if (searchResults.Count == 0) - { - _logger.Info("TVDb Provider - Could not find " + name + ". Check name on Thetvdb.org."); - } - - return searchResults; + return list + .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(i => list.IndexOf(i)) + .Select(i => i.Item2) + .ToList(); } - private RemoteSearchResult GetSeriesSearchResultFromSubTree(XmlReader reader, string comparableName) + private Tuple<List<string>, RemoteSearchResult> GetSeriesSearchResultFromSubTree(XmlReader reader) { var searchResult = new RemoteSearchResult { SearchProviderName = Name }; - var titles = new List<string>(); + var tvdbTitles = new List<string>(); string seriesId = null; reader.MoveToContent(); @@ -621,7 +679,7 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrWhiteSpace(val)) { - titles.Add(GetComparableName(val)); + tvdbTitles.Add(GetComparableName(val)); } break; } @@ -631,7 +689,7 @@ namespace MediaBrowser.Providers.TV var val = reader.ReadElementContentAsString(); var alias = (val ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(GetComparableName); - titles.AddRange(alias); + tvdbTitles.AddRange(alias); break; } @@ -695,22 +753,15 @@ namespace MediaBrowser.Providers.TV } } - foreach (var title in titles) + if (tvdbTitles.Count == 0) { - if (string.Equals(title, comparableName, StringComparison.OrdinalIgnoreCase)) - { - if (!string.IsNullOrWhiteSpace(seriesId)) - { - searchResult.Name = title; - searchResult.SetProviderId(MetadataProviders.Tvdb, seriesId); - return searchResult; - } - break; - } - _logger.Info("TVDb Provider - " + title + " did not match " + comparableName); + return null; } - return null; + searchResult.Name = tvdbTitles.FirstOrDefault(); + searchResult.SetProviderId(MetadataProviders.Tvdb, seriesId); + + return new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, searchResult); } /// <summary> @@ -1081,6 +1132,12 @@ namespace MediaBrowser.Providers.TV { switch (reader.Name) { + case "id": + { + item.SetProviderId(MetadataProviders.Tvdb.ToString(), (reader.ReadElementContentAsString() ?? string.Empty).Trim()); + break; + } + case "SeriesName": { item.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim(); @@ -1100,26 +1157,26 @@ namespace MediaBrowser.Providers.TV } case "Airs_DayOfWeek": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) { - item.AirDays = TVUtils.GetAirDays(val); + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.AirDays = TVUtils.GetAirDays(val); + } + break; } - break; - } case "Airs_Time": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) { - item.AirTime = val; + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.AirTime = val; + } + break; } - break; - } case "ContentRating": { @@ -1256,7 +1313,7 @@ namespace MediaBrowser.Providers.TV if (vals.Count > 0) { - item.Genres.Clear(); + item.Genres = Array.Empty<string>(); foreach (var genre in vals) { @@ -1369,6 +1426,9 @@ namespace MediaBrowser.Providers.TV var absoluteNumber = -1; var lastUpdateString = string.Empty; + var dvdSeasonNumber = -1; + var dvdEpisodeNumber = -1.0; + using (var streamReader = new StringReader(xml)) { // Use XmlReader for best performance @@ -1404,6 +1464,40 @@ namespace MediaBrowser.Providers.TV break; } + case "Combined_episodenumber": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + float num; + + if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) + { + dvdEpisodeNumber = num; + } + } + + break; + } + + case "Combined_season": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + float num; + + if (float.TryParse(val, NumberStyles.Any, _usCulture, out num)) + { + dvdSeasonNumber = Convert.ToInt32(num); + } + } + + break; + } + case "absolute_number": { var val = reader.ReadElementContentAsString(); @@ -1493,6 +1587,27 @@ namespace MediaBrowser.Providers.TV } } } + + if (dvdSeasonNumber != -1 && dvdEpisodeNumber != -1 && (dvdSeasonNumber != seasonNumber || dvdEpisodeNumber != episodeNumber)) + { + file = Path.Combine(seriesDataPath, string.Format("episode-dvd-{0}-{1}.xml", dvdSeasonNumber, dvdEpisodeNumber)); + + // Only save the file if not already there, or if the episode has changed + if (hasEpisodeChanged || !_fileSystem.FileExists(file)) + { + using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true)) + { + using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings + { + Encoding = Encoding.UTF8, + Async = true + })) + { + await writer.WriteRawAsync(xml).ConfigureAwait(false); + } + } + } + } } /// <summary> @@ -1518,6 +1633,13 @@ namespace MediaBrowser.Providers.TV return seriesDataPath; } + if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out seriesId) && !string.IsNullOrEmpty(seriesId)) + { + var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId); + + return seriesDataPath; + } + return null; } diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 104bcb5ae0..6d5f4abbbf 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.TV public string UrlFormatString { - get { return "http://tvlistings.zap2it.com/tv/dexter/{0}?aid=zap2it"; } + get { return "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; } } public bool Supports(IHasProviderIds item) @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV public string UrlFormatString { - get { return "https://thetvdb.com/index.php?tab=series&id={0}"; } + get { return TvdbPrescanTask.TvdbBaseUrl + "?tab=series&id={0}"; } } public bool Supports(IHasProviderIds item) @@ -88,7 +88,7 @@ namespace MediaBrowser.Providers.TV public string UrlFormatString { - get { return "https://thetvdb.com/index.php?tab=episode&id={0}"; } + get { return TvdbPrescanTask.TvdbBaseUrl + "?tab=episode&id={0}"; } } public bool Supports(IHasProviderIds item) |
