diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index a0055f4e6b..ca56e40af8 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -11,13 +11,16 @@ using System.Threading.Tasks; namespace MediaBrowser.Api { - [Route("/Items/{Id}/Refresh", "POST")] - [Api(Description = "Refreshes metadata for an item")] - public class RefreshItem : IReturnVoid + public class BaseRefreshRequest : IReturnVoid { [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool Forced { get; set; } + } + [Route("/Items/{Id}/Refresh", "POST")] + [Api(Description = "Refreshes metadata for an item")] + public class RefreshItem : BaseRefreshRequest + { [ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool Recursive { get; set; } @@ -27,66 +30,48 @@ namespace MediaBrowser.Api [Route("/Artists/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for an artist")] - public class RefreshArtist : IReturnVoid + public class RefreshArtist : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } [Route("/Genres/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for a genre")] - public class RefreshGenre : IReturnVoid + public class RefreshGenre : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } [Route("/MusicGenres/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for a music genre")] - public class RefreshMusicGenre : IReturnVoid + public class RefreshMusicGenre : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } [Route("/GameGenres/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for a game genre")] - public class RefreshGameGenre : IReturnVoid + public class RefreshGameGenre : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } [Route("/Persons/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for a person")] - public class RefreshPerson : IReturnVoid + public class RefreshPerson : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } [Route("/Studios/{Name}/Refresh", "POST")] [Api(Description = "Refreshes metadata for a studio")] - public class RefreshStudio : IReturnVoid + public class RefreshStudio : BaseRefreshRequest { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } @@ -132,11 +117,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -157,11 +138,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -182,11 +159,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -207,11 +180,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -232,11 +201,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -257,11 +222,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -291,11 +252,7 @@ namespace MediaBrowser.Api try { - await item.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); if (item.IsFolder) { @@ -328,13 +285,11 @@ namespace MediaBrowser.Api /// Task. private async Task RefreshCollectionFolderChildren(RefreshItem request, CollectionFolder collectionFolder) { + var options = GetRefreshOptions(request); + foreach (var child in collectionFolder.Children.ToList()) { - await child.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = request.Forced, - - }, CancellationToken.None).ConfigureAwait(false); + await child.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); if (child.IsFolder) { @@ -344,5 +299,15 @@ namespace MediaBrowser.Api } } } + + private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request) + { + return new MetadataRefreshOptions + { + MetadataRefreshMode = request.Forced ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.EnsureMetadata, + ImageRefreshMode = request.Forced ? ImageRefreshMode.FullRefresh : ImageRefreshMode.Default, + ReplaceAllMetadata = request.Forced + }; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 61bf14543f..3bdfc3c444 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -1,8 +1,8 @@ -using System; +using MediaBrowser.Model.Configuration; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities.TV { @@ -57,6 +57,12 @@ namespace MediaBrowser.Controller.Entities.TV /// /// The absolute episode number. public int? AbsoluteEpisodeNumber { get; set; } + + /// + /// This is the ending episode number for double episodes. + /// + /// The index number. + public int? IndexNumberEnd { get; set; } /// /// We want to group into series not show individually in an index @@ -89,7 +95,7 @@ namespace MediaBrowser.Controller.Entities.TV return value; } - var season = Parent as Season; + var season = Season; return season != null ? season.IndexNumber : null; } @@ -140,10 +146,6 @@ namespace MediaBrowser.Controller.Entities.TV get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; } } - /// - /// The _series - /// - private Series _series; /// /// This Episode's Series Instance /// @@ -151,14 +153,14 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public Series Series { - get { return _series ?? (_series = FindParent()); } + get { return FindParent(); } } - /// - /// This is the ending episode number for double episodes. - /// - /// The index number. - public int? IndexNumberEnd { get; set; } + [IgnoreDataMember] + public Season Season + { + get { return FindParent(); } + } /// /// Creates the name of the sort. @@ -217,7 +219,7 @@ namespace MediaBrowser.Controller.Entities.TV get { // First see if the parent is a Season - var season = Parent as Season; + var season = Season; if (season != null) { @@ -229,7 +231,7 @@ namespace MediaBrowser.Controller.Entities.TV // Parent is a Series if (seasonNumber.HasValue) { - var series = Parent as Series; + var series = Series; if (series != null) { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 7bc8154239..8a11cc9a0b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -144,7 +144,6 @@ - diff --git a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs index 86b508012d..e77cd14d17 100644 --- a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs @@ -1,7 +1,8 @@ -using System; +using MediaBrowser.Model.Entities; +using System; +using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.MediaInfo { @@ -35,6 +36,18 @@ namespace MediaBrowser.Controller.MediaInfo /// Task. Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken); + /// + /// Extracts the image. + /// + /// The input files. + /// The type. + /// if set to true [is audio]. + /// The threed format. + /// The offset. + /// The cancellation token. + /// Task{Stream}. + Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); + /// /// Extracts the text subtitle. /// diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs index a53222b5a1..de75c62e9b 100644 --- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs @@ -1,4 +1,5 @@ -using System.Threading; +using MediaBrowser.Controller.Library; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers @@ -10,6 +11,6 @@ namespace MediaBrowser.Controller.Providers public interface ICustomMetadataProvider : IMetadataProvider, ICustomMetadataProvider where TItemType : IHasMetadata { - Task FetchAsync(TItemType item, CancellationToken cancellationToken); + Task FetchAsync(TItemType item, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs b/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs deleted file mode 100644 index 0f5dea5f6e..0000000000 --- a/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace MediaBrowser.Controller.Providers -{ - /// - /// Marker interface for a provider that always runs - /// - public interface IDynamicInfoProvider - { - } -} diff --git a/MediaBrowser.Controller/Providers/IHasMetadata.cs b/MediaBrowser.Controller/Providers/IHasMetadata.cs index 1e2a76b792..3fba73a28b 100644 --- a/MediaBrowser.Controller/Providers/IHasMetadata.cs +++ b/MediaBrowser.Controller/Providers/IHasMetadata.cs @@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.Providers /// System.String. string GetPreferredMetadataCountryCode(); + /// + /// Gets the date modified. + /// + /// The date modified. + DateTime DateModified { get; } + /// /// Gets the locked fields. /// diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs index 5c3ebd9acf..ed7cdc8b2b 100644 --- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Providers public ImageType Type { get; set; } } - public interface IDynamicImageProvider : ILocalImageProvider + public interface IDynamicImageProvider : IImageProvider { /// /// Gets the supported images. diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemId.cs index 3dbaa78fa3..9be6b783cb 100644 --- a/MediaBrowser.Controller/Providers/ItemId.cs +++ b/MediaBrowser.Controller/Providers/ItemId.cs @@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Providers /// /// The year. public int? Year { get; set; } + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } public ItemId() { @@ -45,11 +47,17 @@ namespace MediaBrowser.Controller.Providers /// /// The album artist. public string AlbumArtist { get; set; } + /// - /// Gets or sets the artist music brainz identifier. + /// Gets or sets the artist provider ids. /// - /// The artist music brainz identifier. - public string ArtistMusicBrainzId { get; set; } + /// The artist provider ids. + public Dictionary ArtistProviderIds { get; set; } + + public AlbumId() + { + ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } } public class GameId : ItemId @@ -69,4 +77,16 @@ namespace MediaBrowser.Controller.Providers /// The path. public string Path { get; set; } } + + public class EpisodeId : ItemId + { + public Dictionary SeriesProviderIds { get; set; } + + public int? IndexNumberEnd { get; set; } + + public EpisodeId() + { + SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } } diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs new file mode 100644 index 0000000000..23f370974c --- /dev/null +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Common.IO; +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.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.AdultVideos +{ + class AdultVideoMetadataService : MetadataService + { + private readonly ILibraryManager _libraryManager; + + public AdultVideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// + /// Merges the specified source. + /// + /// The source. + /// The target. + /// The locked fields. + /// if set to true [replace data]. + /// if set to true [merge metadata settings]. + protected override void MergeData(AdultVideo source, AdultVideo target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(AdultVideo item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs new file mode 100644 index 0000000000..3b6439b4b4 --- /dev/null +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs @@ -0,0 +1,59 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Movies; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.AdultVideos +{ + class AdultVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider + { + private readonly ILogger _logger; + + public AdultVideoXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + public async Task> GetMetadata(string path, CancellationToken cancellationToken) + { + path = GetXmlFile(path).FullName; + + var result = new MetadataResult(); + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + result.Item = new AdultVideo(); + + new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + result.HasMetadata = true; + } + catch (FileNotFoundException) + { + result.HasMetadata = false; + } + finally + { + XmlParsingResourcePool.Release(); + } + + return result; + } + + public string Name + { + get { return "Media Browser Xml"; } + } + + protected override FileInfo GetXmlFile(string path) + { + return MovieXmlProvider.GetXmlFileInfo(path, FileSystem); + } + } +} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 68602b1592..2e0e21a463 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs @@ -1,10 +1,8 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs index afa123bf7e..8ca34eb05a 100644 --- a/MediaBrowser.Providers/Games/GameMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameMetadataService.cs @@ -1,10 +1,8 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs index 9e5532a27e..6dd1b1bbc1 100644 --- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs @@ -1,10 +1,8 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs index b0c916a613..01adb3ac34 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs @@ -1,10 +1,8 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs index 02d5c1a796..037fedd794 100644 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -1,10 +1,8 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index da82dcb3fb..c817180c51 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -230,13 +231,23 @@ namespace MediaBrowser.Providers.Manager protected virtual TIdType GetId(TItemType item) { - return new TIdType + var id = new TIdType { MetadataCountryCode = item.GetPreferredMetadataCountryCode(), MetadataLanguage = item.GetPreferredMetadataLanguage(), Name = item.Name, ProviderIds = item.ProviderIds }; + + var baseItem = item as BaseItem; + + if (baseItem != null) + { + id.IndexNumber = baseItem.IndexNumber; + id.ParentIndexNumber = baseItem.ParentIndexNumber; + } + + return id; } public bool CanRefresh(IHasMetadata item) @@ -253,6 +264,7 @@ namespace MediaBrowser.Providers.Manager }; var temp = CreateNew(); + temp.Path = item.Path; // If replacing all metadata, run internet providers first if (options.ReplaceAllMetadata) @@ -313,29 +325,32 @@ namespace MediaBrowser.Providers.Manager foreach (var provider in providers.OfType>()) { - Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); - - try - { - await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false); - - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload; - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - refreshResult.Status = ProviderRefreshStatus.CompletedWithErrors; - refreshResult.ErrorMessage = ex.Message; - Logger.ErrorException("Error in {0}", ex, provider.Name); - } + await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false); } return refreshResult; } + private async Task RunCustomProvider(ICustomMetadataProvider provider, TItemType item, RefreshResult refreshResult, CancellationToken cancellationToken) + { + Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); + + try + { + refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + refreshResult.Status = ProviderRefreshStatus.CompletedWithErrors; + refreshResult.ErrorMessage = ex.Message; + Logger.ErrorException("Error in {0}", ex, provider.Name); + } + } + protected virtual TItemType CreateNew() { return new TItemType(); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 6b0ea7ed47..dbfc97d517 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -246,11 +246,7 @@ namespace MediaBrowser.Providers.Manager cancellationToken.ThrowIfCancellationRequested(); - // Don't clog up the log with these providers - if (!(provider is IDynamicInfoProvider)) - { - _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); - } + _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); try { @@ -637,7 +633,7 @@ namespace MediaBrowser.Providers.Manager })); // Fetchers - list.AddRange(providers.Where(i => !(i is ILocalMetadataProvider)).Select(i => new MetadataPlugin + list.AddRange(providers.Where(i => (i is IRemoteMetadataProvider)).Select(i => new MetadataPlugin { Name = i.Name, Type = MetadataPluginType.MetadataFetcher diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 0ac26330a6..ad8465009d 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -64,6 +64,7 @@ + @@ -89,7 +90,11 @@ + + + + @@ -107,6 +112,8 @@ + + @@ -150,9 +157,9 @@ - - - + + + diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index e897eb1eb1..ad4630dcf6 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -141,109 +141,5 @@ namespace MediaBrowser.Providers.MediaInfo { } - - /// - /// Normalizes the FF probe result. - /// - /// The result. - protected void NormalizeFFProbeResult(InternalMediaInfoResult result) - { - if (result.format != null && result.format.tags != null) - { - result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); - } - - if (result.streams != null) - { - // Convert all dictionaries to case insensitive - foreach (var stream in result.streams) - { - if (stream.tags != null) - { - stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); - } - - if (stream.disposition != null) - { - stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); - } - } - } - } - - /// - /// Gets a string from an FFProbeResult tags dictionary - /// - /// The tags. - /// The key. - /// System.String. - protected string GetDictionaryValue(Dictionary tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - /// - /// Gets an int from an FFProbeResult tags dictionary - /// - /// The tags. - /// The key. - /// System.Nullable{System.Int32}. - protected int? GetDictionaryNumericValue(Dictionary tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - int i; - - if (int.TryParse(val, out i)) - { - return i; - } - } - - return null; - } - - /// - /// Gets a DateTime from an FFProbeResult tags dictionary - /// - /// The tags. - /// The key. - /// System.Nullable{DateTime}. - protected DateTime? GetDictionaryDateTime(Dictionary tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - DateTime i; - - if (DateTime.TryParse(val, out i)) - { - return i.ToUniversalTime(); - } - } - - return null; - } - - /// - /// Converts a dictionary to case insensitive - /// - /// The dict. - /// Dictionary{System.StringSystem.String}. - private Dictionary ConvertDictionaryToCaseInSensitive(Dictionary dict) - { - return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs index d27b65e2aa..bae719eea0 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - NormalizeFFProbeResult(result); + FFProbeHelpers.NormalizeFFProbeResult(result); cancellationToken.ThrowIfCancellationRequested(); @@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo /// The tags. private void FetchDataFromTags(Audio audio, Dictionary tags) { - var title = GetDictionaryValue(tags, "title"); + var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); // Only set Name if title was found in the dictionary if (!string.IsNullOrEmpty(title)) @@ -114,7 +114,7 @@ namespace MediaBrowser.Providers.MediaInfo { audio.People.Clear(); - var composer = GetDictionaryValue(tags, "composer"); + var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); if (!string.IsNullOrWhiteSpace(composer)) { @@ -125,9 +125,9 @@ namespace MediaBrowser.Providers.MediaInfo } } - audio.Album = GetDictionaryValue(tags, "album"); + audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); - var artist = GetDictionaryValue(tags, "artist"); + var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); if (string.IsNullOrWhiteSpace(artist)) { @@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo } // Several different forms of albumartist - audio.AlbumArtist = GetDictionaryValue(tags, "albumartist") ?? GetDictionaryValue(tags, "album artist") ?? GetDictionaryValue(tags, "album_artist"); + audio.AlbumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album artist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); // Track number audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); @@ -150,10 +150,10 @@ namespace MediaBrowser.Providers.MediaInfo // Disc number audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); - audio.ProductionYear = GetDictionaryNumericValue(tags, "date"); + audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); // Several different forms of retaildate - audio.PremiereDate = GetDictionaryDateTime(tags, "retaildate") ?? GetDictionaryDateTime(tags, "retail date") ?? GetDictionaryDateTime(tags, "retail_date"); + audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date"); // If we don't have a ProductionYear try and get it from PremiereDate if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) @@ -219,7 +219,7 @@ namespace MediaBrowser.Providers.MediaInfo /// Name of the tag. private void FetchStudios(Audio audio, Dictionary tags, string tagName) { - var val = GetDictionaryValue(tags, tagName); + var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); if (!string.IsNullOrEmpty(val)) { @@ -240,7 +240,7 @@ namespace MediaBrowser.Providers.MediaInfo /// The tags. private void FetchGenres(Audio audio, Dictionary tags) { - var val = GetDictionaryValue(tags, "genre"); + var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); if (!string.IsNullOrEmpty(val)) { @@ -261,7 +261,7 @@ namespace MediaBrowser.Providers.MediaInfo /// System.Nullable{System.Int32}. private int? GetDictionaryDiscValue(Dictionary tags, string tagName) { - var disc = GetDictionaryValue(tags, tagName); + var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); if (!string.IsNullOrEmpty(disc)) { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs b/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs new file mode 100644 index 0000000000..5a4b2beb26 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs @@ -0,0 +1,113 @@ +using MediaBrowser.Controller.MediaInfo; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Providers.MediaInfo +{ + public static class FFProbeHelpers + { + /// + /// Normalizes the FF probe result. + /// + /// The result. + public static void NormalizeFFProbeResult(InternalMediaInfoResult result) + { + if (result.format != null && result.format.tags != null) + { + result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); + } + + if (result.streams != null) + { + // Convert all dictionaries to case insensitive + foreach (var stream in result.streams) + { + if (stream.tags != null) + { + stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); + } + + if (stream.disposition != null) + { + stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); + } + } + } + } + + /// + /// Gets a string from an FFProbeResult tags dictionary + /// + /// The tags. + /// The key. + /// System.String. + public static string GetDictionaryValue(Dictionary tags, string key) + { + if (tags == null) + { + return null; + } + + string val; + + tags.TryGetValue(key, out val); + return val; + } + + /// + /// Gets an int from an FFProbeResult tags dictionary + /// + /// The tags. + /// The key. + /// System.Nullable{System.Int32}. + public static int? GetDictionaryNumericValue(Dictionary tags, string key) + { + var val = GetDictionaryValue(tags, key); + + if (!string.IsNullOrEmpty(val)) + { + int i; + + if (int.TryParse(val, out i)) + { + return i; + } + } + + return null; + } + + /// + /// Gets a DateTime from an FFProbeResult tags dictionary + /// + /// The tags. + /// The key. + /// System.Nullable{DateTime}. + public static DateTime? GetDictionaryDateTime(Dictionary tags, string key) + { + var val = GetDictionaryValue(tags, key); + + if (!string.IsNullOrEmpty(val)) + { + DateTime i; + + if (DateTime.TryParse(val, out i)) + { + return i.ToUniversalTime(); + } + } + + return null; + } + + /// + /// Converts a dictionary to case insensitive + /// + /// The dict. + /// Dictionary{System.StringSystem.String}. + private static Dictionary ConvertDictionaryToCaseInSensitive(Dictionary dict) + { + return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs new file mode 100644 index 0000000000..1f3723653e --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -0,0 +1,103 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.MediaInfo +{ + public class FFProbeProvider : ICustomMetadataProvider, + ICustomMetadataProvider, + ICustomMetadataProvider, + ICustomMetadataProvider, + ICustomMetadataProvider, + IHasChangeMonitor + { + private readonly ILogger _logger; + private readonly IIsoManager _isoManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IItemRepository _itemRepo; + private readonly IBlurayExaminer _blurayExaminer; + private readonly ILocalizationManager _localization; + + public string Name + { + get { return "ffprobe"; } + } + + public Task FetchAsync(Episode item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task FetchAsync(MusicVideo item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task FetchAsync(Movie item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task FetchAsync(AdultVideo item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task FetchAsync(LiveTvVideoRecording item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization) + { + _logger = logger; + _isoManager = isoManager; + _mediaEncoder = mediaEncoder; + _itemRepo = itemRepo; + _blurayExaminer = blurayExaminer; + _localization = localization; + } + + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.Unspecified); + public Task FetchVideoInfo(T item, CancellationToken cancellationToken) + where T : Video + { + if (item.LocationType != LocationType.FileSystem) + { + return _cachedTask; + } + + if (item.VideoType == VideoType.Iso && !_isoManager.CanMount(item.Path)) + { + return _cachedTask; + } + + if (item.VideoType == VideoType.HdDvd) + { + return _cachedTask; + } + + var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization); + + return prober.ProbeVideo(item, cancellationToken); + } + + public bool HasChanged(IHasMetadata item, DateTime date) + { + return item.DateModified > date; + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs new file mode 100644 index 0000000000..8adb75839d --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -0,0 +1,587 @@ +using DvdLib.Ifo; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.MediaInfo +{ + public class FFProbeVideoInfo + { + private readonly ILogger _logger; + private readonly IIsoManager _isoManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IItemRepository _itemRepo; + private readonly IBlurayExaminer _blurayExaminer; + private readonly ILocalizationManager _localization; + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization) + { + _logger = logger; + _isoManager = isoManager; + _mediaEncoder = mediaEncoder; + _itemRepo = itemRepo; + _blurayExaminer = blurayExaminer; + _localization = localization; + } + + public async Task ProbeVideo(T item, CancellationToken cancellationToken) + where T : Video + { + var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); + + try + { + OnPreFetch(item, isoMount); + + // If we didn't find any satisfying the min length, just take them all + if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd)) + { + if (item.PlayableStreamFileNames.Count == 0) + { + _logger.Error("No playable vobs found in dvd structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } + + var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + FFProbeHelpers.NormalizeFFProbeResult(result); + + cancellationToken.ThrowIfCancellationRequested(); + + await Fetch(item, cancellationToken, result, isoMount).ConfigureAwait(false); + + } + finally + { + if (isoMount != null) + { + isoMount.Dispose(); + } + } + + return ItemUpdateType.MetadataImport; + } + + private async Task GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var type = InputType.File; + var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath }; + + var video = item as Video; + + if (video != null) + { + inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); + } + + return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); + } + + protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount) + { + if (data.format != null) + { + // For dvd's this may not always be accurate, so don't set the runtime if the item already has one + var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; + + if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration)) + { + video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; + } + } + + var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams; + + var chapters = data.Chapters ?? new List(); + + if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) + { + var inputPath = isoMount != null ? isoMount.MountedPath : video.Path; + FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken); + } + + AddExternalSubtitles(video, mediaStreams); + + FetchWtvInfo(video, data); + + video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); + + if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) + { + AddDummyChapters(video, chapters); + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + video.VideoBitRate = videoStream == null ? null : videoStream.BitRate; + video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; + + video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); + + await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false); + + await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); + + await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + } + + private void FetchBdInfo(BaseItem item, List chapters, List mediaStreams, string inputPath, CancellationToken cancellationToken) + { + var video = (Video)item; + + var result = GetBDInfo(inputPath); + + cancellationToken.ThrowIfCancellationRequested(); + + int? currentHeight = null; + int? currentWidth = null; + int? currentBitRate = null; + + var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + + // Grab the values that ffprobe recorded + if (videoStream != null) + { + currentBitRate = videoStream.BitRate; + currentWidth = videoStream.Width; + currentHeight = videoStream.Height; + } + + // Fill video properties from the BDInfo result + Fetch(video, mediaStreams, result, chapters); + + videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + + // Use the ffprobe values if these are empty + if (videoStream != null) + { + videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; + videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; + videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; + } + } + + private bool IsEmpty(int? num) + { + return !num.HasValue || num.Value == 0; + } + + /// The chapters. + private void Fetch(Video video, List mediaStreams, BlurayDiscInfo stream, List chapters) + { + // Check all input for null/empty/zero + + mediaStreams.Clear(); + mediaStreams.AddRange(stream.MediaStreams); + + video.MainFeaturePlaylistName = stream.PlaylistName; + + if (stream.RunTimeTicks.HasValue && stream.RunTimeTicks.Value > 0) + { + video.RunTimeTicks = stream.RunTimeTicks; + } + + video.PlayableStreamFileNames = stream.Files.ToList(); + + if (stream.Chapters != null) + { + chapters.Clear(); + + chapters.AddRange(stream.Chapters.Select(c => new ChapterInfo + { + StartPositionTicks = TimeSpan.FromSeconds(c).Ticks + + })); + } + } + + /// + /// Gets information about the longest playlist on a bdrom + /// + /// The path. + /// VideoStream. + private BlurayDiscInfo GetBDInfo(string path) + { + return _blurayExaminer.GetDiscInfo(path); + } + + private void FetchWtvInfo(Video video, InternalMediaInfoResult data) + { + if (data.format == null || data.format.tags == null) + { + return; + } + + if (video.Genres.Count == 0) + { + if (!video.LockedFields.Contains(MetadataFields.Genres)) + { + var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); + + if (!string.IsNullOrEmpty(genres)) + { + video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => i.Trim()) + .ToList(); + } + } + } + + if (string.IsNullOrEmpty(video.Overview)) + { + if (!video.LockedFields.Contains(MetadataFields.Overview)) + { + var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); + + if (!string.IsNullOrWhiteSpace(overview)) + { + video.Overview = overview; + } + } + } + + if (string.IsNullOrEmpty(video.OfficialRating)) + { + var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); + + if (!string.IsNullOrWhiteSpace(officialRating)) + { + if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) + { + video.OfficialRating = officialRating; + } + } + } + + if (video.People.Count == 0) + { + if (!video.LockedFields.Contains(MetadataFields.Cast)) + { + var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); + + if (!string.IsNullOrEmpty(people)) + { + video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor }) + .ToList(); + } + } + } + + if (!video.ProductionYear.HasValue) + { + var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); + + if (!string.IsNullOrWhiteSpace(year)) + { + int val; + + if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) + { + video.ProductionYear = val; + } + } + } + } + + private IEnumerable SubtitleExtensions + { + get + { + return new[] { ".srt", ".ssa", ".ass" }; + } + } + + /// + /// Adds the external subtitles. + /// + /// The video. + /// The current streams. + private void AddExternalSubtitles(Video video, List currentStreams) + { + var useParent = !video.ResolveArgs.IsDirectory; + + if (useParent && video.Parent == null) + { + return; + } + + var fileSystemChildren = useParent + ? video.Parent.ResolveArgs.FileSystemChildren + : video.ResolveArgs.FileSystemChildren; + + var startIndex = currentStreams.Count; + var streams = new List(); + + var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + foreach (var file in fileSystemChildren + .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) + { + var fullName = file.FullName; + + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + + // If the subtitle file matches the video file name + if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + streams.Add(new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') + }); + } + else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) + { + // Support xbmc naming conventions - 300.spanish.srt + var language = fileNameWithoutExtension.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)); + + if (culture != null) + { + language = culture.ThreeLetterISOLanguageName; + } + + streams.Add(new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), + Language = language + }); + } + } + + currentStreams.AddRange(streams); + } + + /// + /// The dummy chapter duration + /// + private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; + + /// + /// Adds the dummy chapters. + /// + /// The video. + /// The chapters. + private void AddDummyChapters(Video video, List chapters) + { + var runtime = video.RunTimeTicks ?? 0; + + if (runtime < 0) + { + throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime)); + } + + if (runtime < _dummyChapterDuration) + { + return; + } + + long currentChapterTicks = 0; + var index = 1; + + // Limit to 100 chapters just in case there's some incorrect metadata here + while (currentChapterTicks < runtime && index < 100) + { + chapters.Add(new ChapterInfo + { + Name = "Chapter " + index, + StartPositionTicks = currentChapterTicks + }); + + index++; + currentChapterTicks += _dummyChapterDuration; + } + } + + /// + /// Called when [pre fetch]. + /// + /// The item. + /// The mount. + private void OnPreFetch(Video item, IIsoMount mount) + { + if (item.VideoType == VideoType.Iso) + { + item.IsoType = DetermineIsoType(mount); + } + + if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd)) + { + FetchFromDvdLib(item, mount); + } + } + + private void FetchFromDvdLib(Video item, IIsoMount mount) + { + var path = mount == null ? item.Path : mount.MountedPath; + var dvd = new Dvd(path); + + item.RunTimeTicks = dvd.Titles.Select(GetRuntime).Max(); + + var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault(); + + uint? titleNumber = null; + + if (primaryTitle != null) + { + titleNumber = primaryTitle.TitleNumber; + } + + item.PlayableStreamFileNames = GetPrimaryPlaylistVobFiles(item, mount, titleNumber) + .Select(Path.GetFileName) + .ToList(); + } + + private long GetRuntime(Title title) + { + return title.ProgramChains + .Select(i => (TimeSpan)i.PlaybackTime) + .Select(i => i.Ticks) + .Sum(); + } + + /// + /// Mounts the iso if needed. + /// + /// The item. + /// The cancellation token. + /// IsoMount. + protected Task MountIsoIfNeeded(Video item, CancellationToken cancellationToken) + { + if (item.VideoType == VideoType.Iso) + { + return _isoManager.Mount(item.Path, cancellationToken); + } + + return Task.FromResult(null); + } + + /// + /// Determines the type of the iso. + /// + /// The iso mount. + /// System.Nullable{IsoType}. + private IsoType? DetermineIsoType(IIsoMount isoMount) + { + var folders = Directory.EnumerateDirectories(isoMount.MountedPath).Select(Path.GetFileName).ToList(); + + if (folders.Contains("video_ts", StringComparer.OrdinalIgnoreCase)) + { + return IsoType.Dvd; + } + if (folders.Contains("bdmv", StringComparer.OrdinalIgnoreCase)) + { + return IsoType.BluRay; + } + + return null; + } + + private IEnumerable GetPrimaryPlaylistVobFiles(Video video, IIsoMount isoMount, uint? titleNumber) + { + // min size 300 mb + const long minPlayableSize = 314572800; + + var root = isoMount != null ? isoMount.MountedPath : video.Path; + + // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size + // Once we reach a file that is at least the minimum, return all subsequent ones + var allVobs = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories) + .Where(file => string.Equals(Path.GetExtension(file), ".vob", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (allVobs.Count == 0) + { + _logger.Error("No vobs found in dvd structure."); + return new List(); + } + + if (titleNumber.HasValue) + { + var prefix = string.Format("VTS_0{0}_", titleNumber.Value.ToString(_usCulture)); + var vobs = allVobs.Where(i => Path.GetFileName(i).StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (vobs.Count > 0) + { + return vobs; + } + + _logger.Debug("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", video.Path); + } + + var files = allVobs + .SkipWhile(f => new FileInfo(f).Length < minPlayableSize) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (files.Count == 0) + { + _logger.Warn("Vob size filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + + // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file + if (files.Count > 0) + { + var parts = Path.GetFileNameWithoutExtension(files[0]).Split('_'); + + if (parts.Length == 3) + { + var title = parts[1]; + + files = files.TakeWhile(f => + { + var fileParts = Path.GetFileNameWithoutExtension(f).Split('_'); + + return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); + + }).ToList(); + + // If this resulted in not getting any vobs, just take them all + if (files.Count == 0) + { + _logger.Warn("Vob filename filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + } + } + + return files; + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs index afbea7f8bf..8d69e6ba23 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - NormalizeFFProbeResult(result); + FFProbeHelpers.NormalizeFFProbeResult(result); cancellationToken.ThrowIfCancellationRequested(); @@ -401,7 +401,7 @@ namespace MediaBrowser.Providers.MediaInfo { if (!video.LockedFields.Contains(MetadataFields.Genres)) { - var genres = GetDictionaryValue(data.format.tags, "genre"); + var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); if (!string.IsNullOrEmpty(genres)) { @@ -417,7 +417,7 @@ namespace MediaBrowser.Providers.MediaInfo { if (!video.LockedFields.Contains(MetadataFields.Overview)) { - var overview = GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); + var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); if (!string.IsNullOrWhiteSpace(overview)) { @@ -428,7 +428,7 @@ namespace MediaBrowser.Providers.MediaInfo if (force || string.IsNullOrEmpty(video.OfficialRating)) { - var officialRating = GetDictionaryValue(data.format.tags, "WM/ParentalRating"); + var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); if (!string.IsNullOrWhiteSpace(officialRating)) { @@ -443,7 +443,7 @@ namespace MediaBrowser.Providers.MediaInfo { if (!video.LockedFields.Contains(MetadataFields.Cast)) { - var people = GetDictionaryValue(data.format.tags, "WM/MediaCredits"); + var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); if (!string.IsNullOrEmpty(people)) { @@ -457,7 +457,7 @@ namespace MediaBrowser.Providers.MediaInfo if (force || !video.ProductionYear.HasValue) { - var year = GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); + var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); if (!string.IsNullOrWhiteSpace(year)) { diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 21d3ae3e8e..70b849e1c3 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -1,90 +1,28 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; using System; -using System.Collections.Concurrent; -using System.IO; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers.MediaInfo { - class VideoImageProvider : BaseMetadataProvider + public class VideoImageProvider : IDynamicImageProvider { - /// - /// The _locks - /// - private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); - - /// - /// The _media encoder - /// - private readonly IMediaEncoder _mediaEncoder; private readonly IIsoManager _isoManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IServerConfigurationManager _config; - public VideoImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IIsoManager isoManager) - : base(logManager, configurationManager) + public VideoImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config) { - _mediaEncoder = mediaEncoder; _isoManager = isoManager; - } - - /// - /// Gets a value indicating whether [refresh on version change]. - /// - /// true if [refresh on version change]; otherwise, false. - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - /// - /// Gets the provider version. - /// - /// The provider version. - protected override string ProviderVersion - { - get - { - return "1"; - } - } - - /// - /// Supportses the specified item. - /// - /// The item. - /// true if XXXX, false otherwise - public override bool Supports(BaseItem item) - { - return item.LocationType == LocationType.FileSystem && item is Video; - } - - /// - /// Needses the refresh internal. - /// - /// The item. - /// The provider info. - /// true if XXXX, false otherwise - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - var video = (Video)item; - - if (!QualifiesForExtraction(video)) - { - return false; - } - - return base.NeedsRefreshInternal(item, providerInfo); + _mediaEncoder = mediaEncoder; + _config = config; } /// @@ -94,16 +32,6 @@ namespace MediaBrowser.Providers.MediaInfo /// true if XXXX, false otherwise private bool QualifiesForExtraction(Video item) { - if (!ConfigurationManager.Configuration.EnableVideoImageExtraction) - { - return false; - } - - if (!string.IsNullOrEmpty(item.PrimaryImagePath)) - { - return false; - } - // No support for this if (item.VideoType == VideoType.HdDvd) { @@ -126,137 +54,61 @@ namespace MediaBrowser.Providers.MediaInfo } /// - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// - /// The item. - /// DateTime. - protected override DateTime CompareDate(BaseItem item) - { - return item.DateModified; - } - - /// - /// Gets the priority. + /// The null mount task result /// - /// The priority. - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Last; } - } - - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } + protected readonly Task NullMountTaskResult = Task.FromResult(null); /// - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// Mounts the iso if needed. /// /// The item. - /// if set to true [force]. /// The cancellation token. - /// Task{System.Boolean}. - public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + /// Task{IIsoMount}. + protected Task MountIsoIfNeeded(Video item, CancellationToken cancellationToken) { - item.ValidateImages(); - - var video = (Video)item; - - // Double check this here in case force was used - if (QualifiesForExtraction(video)) + if (item.VideoType == VideoType.Iso) { - try - { - await ExtractImage(video, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - // Swallow this so that we don't keep on trying over and over again - - Logger.ErrorException("Error extracting image for {0}", ex, item.Name); - } + return _isoManager.Mount(item.Path, cancellationToken); } - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; + return NullMountTaskResult; } - /// - /// Extracts the image. - /// - /// The item. - /// The cancellation token. - /// Task. - private async Task ExtractImage(Video item, CancellationToken cancellationToken) + public IEnumerable GetSupportedImages(IHasImages item) { - cancellationToken.ThrowIfCancellationRequested(); - - var path = GetVideoImagePath(item); - - if (!File.Exists(path)) - { - var semaphore = GetLock(path); - - // Acquire a lock - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - // Check again - if (!File.Exists(path)) - { - try - { - var parentPath = Path.GetDirectoryName(path); - - Directory.CreateDirectory(parentPath); - - await ExtractImageInternal(item, path, cancellationToken).ConfigureAwait(false); - } - finally - { - semaphore.Release(); - } - } - else - { - semaphore.Release(); - } - } + return new List { ImageType.Primary }; + } - // Image is already in the cache - item.SetImagePath(ImageType.Primary, path); + public Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) + { + return GetVideoImage((Video)item, cancellationToken); } - /// - /// Extracts the image. - /// - /// The video. - /// The path. - /// The cancellation token. - /// Task. - private async Task ExtractImageInternal(Video video, string path, CancellationToken cancellationToken) + public async Task GetVideoImage(Video item, CancellationToken cancellationToken) { - var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false); + var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); try { // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. // Always use 10 seconds for dvd because our duration could be out of whack - var imageOffset = video.VideoType != VideoType.Dvd && video.RunTimeTicks.HasValue && - video.RunTimeTicks.Value > 0 - ? TimeSpan.FromTicks(Convert.ToInt64(video.RunTimeTicks.Value * .1)) + var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && + item.RunTimeTicks.Value > 0 + ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); InputType type; - var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); + var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, item.LocationType == LocationType.Remote, item.VideoType, item.IsoType, isoMount, item.PlayableStreamFileNames, out type); - await _mediaEncoder.ExtractImage(inputPath, type, false, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false); + var stream = await _mediaEncoder.ExtractImage(inputPath, type, false, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); - video.SetImagePath(ImageType.Primary, path); + return new DynamicImageResponse + { + Format = ImageFormat.Jpg, + HasImage = true, + Stream = stream + }; } finally { @@ -267,63 +119,19 @@ namespace MediaBrowser.Providers.MediaInfo } } - /// - /// The null mount task result - /// - protected readonly Task NullMountTaskResult = Task.FromResult(null); - - /// - /// Mounts the iso if needed. - /// - /// The item. - /// The cancellation token. - /// Task{IIsoMount}. - protected Task MountIsoIfNeeded(Video item, CancellationToken cancellationToken) - { - if (item.VideoType == VideoType.Iso) - { - return _isoManager.Mount(item.Path, cancellationToken); - } - - return NullMountTaskResult; - } - - /// - /// Gets the lock. - /// - /// The filename. - /// SemaphoreSlim. - private SemaphoreSlim GetLock(string filename) + public string Name { - return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + get { return "Embedded Image"; } } - /// - /// Gets the video images data path. - /// - /// The video images data path. - public string VideoImagesPath + public bool Supports(IHasImages item) { - get + if (!_config.Configuration.EnableVideoImageExtraction) { - return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-video-images"); + return false; } - } - /// - /// Gets the audio image path. - /// - /// The item. - /// System.String. - private string GetVideoImagePath(Video item) - { - var filename = item.Path + "_" + item.DateModified.Ticks + "_primary"; - - filename = filename.GetMD5() + ".jpg"; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(VideoImagesPath, prefix, filename); + return item.LocationType == LocationType.FileSystem && item is Video; } } } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 87e6be1f7c..3adc682fca 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -371,6 +371,11 @@ namespace MediaBrowser.Providers.Movies { var path = GetDataFilePath(item); + if (string.IsNullOrEmpty(path)) + { + return _cachedTask; + } + var fileInfo = _fileSystem.GetFileSystemInfo(path); if (fileInfo.Exists) diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs index 67b62548e6..7ae6dffc79 100644 --- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs +++ b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs @@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.Movies try { - await new MovieXmlParser(Logger, _itemRepo).FetchAsync(video, path, cancellationToken).ConfigureAwait(false); + new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken); } finally { diff --git a/MediaBrowser.Providers/Movies/MovieXmlParser.cs b/MediaBrowser.Providers/Movies/MovieXmlParser.cs index 61b73360cc..64038e8537 100644 --- a/MediaBrowser.Providers/Movies/MovieXmlParser.cs +++ b/MediaBrowser.Providers/Movies/MovieXmlParser.cs @@ -1,10 +1,8 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.Threading; -using System.Threading.Tasks; using System.Xml; namespace MediaBrowser.Providers.Movies @@ -14,28 +12,14 @@ namespace MediaBrowser.Providers.Movies /// public class MovieXmlParser : BaseItemXmlParser