diff options
28 files changed, 791 insertions, 492 deletions
diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemId.cs index 1116eb8b5..b3fe5bee5 100644 --- a/MediaBrowser.Controller/Providers/ItemId.cs +++ b/MediaBrowser.Controller/Providers/ItemId.cs @@ -26,6 +26,11 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <value>The provider ids.</value> public Dictionary<string, string> ProviderIds { get; set; } + /// <summary> + /// Gets or sets the year. + /// </summary> + /// <value>The year.</value> + public int? Year { get; set; } public ItemId() { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index eef6f0f2f..424c9c48e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -189,6 +189,7 @@ namespace MediaBrowser.Model.Configuration /// <value><c>true</c> if [enable tv db updates]; otherwise, <c>false</c>.</value> public bool EnableTvDbUpdates { get; set; } public bool EnableTmdbUpdates { get; set; } + public bool EnableFanArtUpdates { get; set; } public bool EnableVideoImageExtraction { get; set; } @@ -243,8 +244,9 @@ namespace MediaBrowser.Model.Configuration LegacyWebSocketPortNumber = 8945; EnableHttpLevelLogging = true; EnableDashboardResponseCaching = true; - EnableVideoImageExtraction = true; + EnableFanArtUpdates = true; + EnableVideoImageExtraction = true; EnableMovieChapterImageExtraction = true; EnableEpisodeChapterImageExtraction = false; EnableOtherVideoChapterImageExtraction = false; diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs new file mode 100644 index 000000000..62b2e627f --- /dev/null +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Movies; +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.BoxSets +{ + public class BoxSetMetadataService : ConcreteMetadataService<BoxSet> + { + private readonly ILibraryManager _libraryManager; + + public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(BoxSet item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs new file mode 100644 index 000000000..8b8c8bffd --- /dev/null +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.BoxSets +{ + /// <summary> + /// Class SeriesProviderFromXml + /// </summary> + public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet> + { + private readonly ILogger _logger; + + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken) + { + path = GetXmlPath(path); + + var result = new MetadataResult<BoxSet>(); + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var item = new BoxSet(); + + new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken); + result.HasMetadata = true; + result.Item = item; + } + catch (FileNotFoundException) + { + result.HasMetadata = false; + } + finally + { + XmlParsingResourcePool.Release(); + } + + return result; + } + + public string Name + { + get { return "Media Browser Xml"; } + } + + protected override string GetXmlPath(string path) + { + return Path.Combine(path, "collection.xml"); + } + } +} diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs new file mode 100644 index 000000000..2fe18fafa --- /dev/null +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -0,0 +1,255 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Providers.Movies; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.BoxSets +{ + public class MovieDbBoxSetProvider : IRemoteMetadataProvider<BoxSet> + { + private readonly CultureInfo _enUs = new CultureInfo("en-US"); + private const string GetCollectionInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; + + private readonly ILogger _logger; + private readonly IJsonSerializer _json; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + + public MovieDbBoxSetProvider(ILogger logger, IJsonSerializer json, IServerConfigurationManager config, IFileSystem fileSystem) + { + _logger = logger; + _json = json; + _config = config; + _fileSystem = fileSystem; + } + + public async Task<MetadataResult<BoxSet>> GetMetadata(ItemId id, CancellationToken cancellationToken) + { + var tmdbId = id.GetProviderId(MetadataProviders.Tmdb); + + // We don't already have an Id, need to fetch it + if (string.IsNullOrEmpty(tmdbId)) + { + tmdbId = await GetTmdbId(id, cancellationToken).ConfigureAwait(false); + } + + var result = new MetadataResult<BoxSet>(); + + if (!string.IsNullOrEmpty(tmdbId)) + { + await EnsureInfo(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); + + var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, id.MetadataLanguage); + + if (!string.IsNullOrEmpty(dataFilePath)) + { + var mainResult = _json.DeserializeFromFile<RootObject>(dataFilePath); + + result.HasMetadata = true; + result.Item = GetItem(mainResult); + } + + } + + return result; + } + + private BoxSet GetItem(RootObject obj) + { + var item = new BoxSet(); + + item.Name = obj.name; + item.Overview = obj.overview; + item.SetProviderId(MetadataProviders.Tmdb, obj.id.ToString(_enUs)); + + return item; + } + + private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + + if (mainResult == null) return; + + var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); + + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + + _json.SerializeToFile(mainResult, dataFilePath); + } + + private async Task<RootObject> FetchMainResult(string id, string language, CancellationToken cancellationToken) + { + var url = string.Format(GetCollectionInfo3, id, MovieDbSearch.ApiKey); + + // Get images in english and with no language + url += "&include_image_language=en,null"; + + if (!string.IsNullOrEmpty(language)) + { + // If preferred language isn't english, get those images too + if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + url += string.Format(",{0}", language); + } + + url += string.Format("&language={0}", language); + } + + cancellationToken.ThrowIfCancellationRequested(); + + RootObject mainResult = null; + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbSearch.AcceptHeader + + }).ConfigureAwait(false)) + { + mainResult = _json.DeserializeFromStream<RootObject>(json); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (mainResult != null && string.IsNullOrEmpty(mainResult.overview)) + { + if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + url = string.Format(GetCollectionInfo3, id, MovieDbSearch.ApiKey) + "&include_image_language=en,null&language=en"; + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbSearch.AcceptHeader + + }).ConfigureAwait(false)) + { + mainResult = _json.DeserializeFromStream<RootObject>(json); + } + + if (String.IsNullOrEmpty(mainResult.overview)) + { + _logger.Error("Unable to find information for (id:" + id + ")"); + return null; + } + } + } + return mainResult; + } + + internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return Task.FromResult(true); + } + } + + return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken); + } + + private Task<string> GetTmdbId(ItemId id, CancellationToken cancellationToken) + { + return new MovieDbSearch(_logger, _json).FindCollectionId(id, cancellationToken); + } + + public string Name + { + get { return "TheMovieDb"; } + } + + private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage) + { + var path = GetDataPath(appPaths, tmdbId); + + var filename = string.Format("all-{0}.json", + preferredLanguage ?? string.Empty); + + return Path.Combine(path, filename); + } + + private static string GetDataPath(IApplicationPaths appPaths, string tmdbId) + { + var dataPath = GetCollectionsDataPath(appPaths); + + return Path.Combine(dataPath, tmdbId); + } + + private static string GetCollectionsDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.DataPath, "tmdb-collections"); + + return dataPath; + } + + internal class Part + { + public string title { get; set; } + public int id { get; set; } + public string release_date { get; set; } + public string poster_path { get; set; } + public string backdrop_path { get; set; } + } + + internal class Backdrop + { + public double aspect_ratio { get; set; } + public string file_path { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public object vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + internal class Poster + { + public double aspect_ratio { get; set; } + public string file_path { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public object vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + internal class Images + { + public List<Backdrop> backdrops { get; set; } + public List<Poster> posters { get; set; } + } + + internal class RootObject + { + public int id { get; set; } + public string name { get; set; } + public string overview { get; set; } + public string poster_path { get; set; } + public string backdrop_path { get; set; } + public List<Part> parts { get; set; } + public Images images { get; set; } + } + } +} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 389e2a275..94209c309 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.GameGenres { - public class GameGenreMetadataService : MetadataService<GameGenre> + public class GameGenreMetadataService : ConcreteMetadataService<GameGenre> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index 83253a190..21addc390 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Genres { - public class GenreMetadataService : MetadataService<Genre> + public class GenreMetadataService : ConcreteMetadataService<Genre> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs index 8c5636580..f63417026 100644 --- a/MediaBrowser.Providers/ImagesByNameProvider.cs +++ b/MediaBrowser.Providers/ImagesByNameProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers public override bool Supports(BaseItem item) { // Only run for these generic types since we are expensive in file i/o - return item is BasePluginFolder || item is CollectionFolder; + return item is ICollectionFolder; } /// <summary> diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs index dd44ba7aa..067894337 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.LiveTv { - public class ChannelMetadataService : MetadataService<LiveTvChannel> + public class ChannelMetadataService : ConcreteMetadataService<LiveTvChannel> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs index da032eb8f..6f08b199a 100644 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.LiveTv { - public class ProgramMetadataService : MetadataService<LiveTvProgram> + public class ProgramMetadataService : ConcreteMetadataService<LiveTvProgram> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/Manager/ConcreteMetadataService.cs b/MediaBrowser.Providers/Manager/ConcreteMetadataService.cs new file mode 100644 index 000000000..3a4bc06ca --- /dev/null +++ b/MediaBrowser.Providers/Manager/ConcreteMetadataService.cs @@ -0,0 +1,20 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Providers.Manager +{ + public abstract class ConcreteMetadataService<TItemType> : MetadataService<TItemType> + where TItemType : IHasMetadata, new() + { + protected ConcreteMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo) + : base(serverConfigurationManager, logger, providerManager, providerRepo) + { + } + + protected override TItemType CreateNew() + { + return new TItemType(); + } + } +} diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index e8b3a6ad6..d946f9cbd 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Manager { public abstract class MetadataService<TItemType> : IMetadataService - where TItemType : IHasMetadata, new() + where TItemType : IHasMetadata { protected readonly IServerConfigurationManager ServerConfigurationManager; protected readonly ILogger Logger; @@ -263,7 +263,7 @@ namespace MediaBrowser.Providers.Manager Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList() }; - var temp = new TItemType(); + var temp = CreateNew(); // If replacing all metadata, run internet providers first if (options.ReplaceAllMetadata) @@ -317,6 +317,8 @@ namespace MediaBrowser.Providers.Manager return refreshResult; } + protected abstract TItemType CreateNew(); + private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken) { var id = GetId(item); diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index b44d3608e..ce996ccfc 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -65,11 +65,14 @@ </ItemGroup> <ItemGroup> <Compile Include="All\LocalImageProvider.cs" /> + <Compile Include="BoxSets\BoxSetMetadataService.cs" /> + <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> <Compile Include="GameGenres\GameGenreMetadataService.cs" /> <Compile Include="Genres\GenreMetadataService.cs" /> <Compile Include="LiveTv\ChannelMetadataService.cs" /> <Compile Include="LiveTv\ChannelXmlProvider.cs" /> <Compile Include="LiveTv\ProgramMetadataService.cs" /> + <Compile Include="Manager\ConcreteMetadataService.cs" /> <Compile Include="Manager\ImageSaver.cs" /> <Compile Include="Manager\ItemImageProvider.cs" /> <Compile Include="Manager\ProviderManager.cs" /> @@ -83,6 +86,7 @@ <Compile Include="Games\GameSystemProviderFromXml.cs" /> <Compile Include="ImageFromMediaLocationProvider.cs" /> <Compile Include="ImagesByNameProvider.cs" /> + <Compile Include="Movies\MovieDbSearch.cs" /> <Compile Include="MusicGenres\MusicGenreImageProvider.cs" /> <Compile Include="GameGenres\GameGenreImageProvider.cs" /> <Compile Include="Genres\GenreImageProvider.cs" /> @@ -92,7 +96,7 @@ <Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" /> <Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" /> <Compile Include="MediaInfo\VideoImageProvider.cs" /> - <Compile Include="Movies\BoxSetProviderFromXml.cs" /> + <Compile Include="BoxSets\BoxSetXmlProvider.cs" /> <Compile Include="Movies\ManualMovieDbImageProvider.cs" /> <Compile Include="Movies\ManualFanartMovieImageProvider.cs" /> <Compile Include="MusicGenres\MusicGenreMetadataService.cs" /> @@ -169,6 +173,7 @@ <Compile Include="TV\TvdbPrescanTask.cs" /> <Compile Include="TV\TvdbSeriesImageProvider.cs" /> <Compile Include="UserRootFolderNameProvider.cs" /> + <Compile Include="Users\UserMetadataService.cs" /> <Compile Include="VirtualItemImageValidator.cs" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Providers/Movies/BoxSetProviderFromXml.cs b/MediaBrowser.Providers/Movies/BoxSetProviderFromXml.cs deleted file mode 100644 index 7c88243b3..000000000 --- a/MediaBrowser.Providers/Movies/BoxSetProviderFromXml.cs +++ /dev/null @@ -1,97 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Movies -{ - /// <summary> - /// Class SeriesProviderFromXml - /// </summary> - public class BoxSetProviderFromXml : BaseMetadataProvider - { - private readonly IFileSystem _fileSystem; - - public BoxSetProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - _fileSystem = fileSystem; - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is BoxSet && item.LocationType == LocationType.FileSystem; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - - private const string XmlFileName = "collection.xml"; - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (xml == null) - { - return false; - } - - return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved; - } - - /// <summary> - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done - /// </summary> - /// <param name="item">The item.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (metadataFile != null) - { - var path = metadataFile.FullName; - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - new BaseItemXmlParser<BoxSet>(Logger).Fetch((BoxSet)item, path, cancellationToken); - } - finally - { - XmlParsingResourcePool.Release(); - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - - return true; - } - - return false; - } - } -} diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 2682cf3c0..3c8fd4612 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -112,11 +112,6 @@ namespace MediaBrowser.Providers.Movies /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return SupportsItem(item); - } - - internal static bool SupportsItem(IHasImages item) - { var trailer = item as Trailer; if (trailer != null) @@ -124,7 +119,7 @@ namespace MediaBrowser.Providers.Movies return !trailer.IsLocalTrailer; } - return item is Movie || item is BoxSet || item is MusicVideo; + return item is Movie || item is MusicVideo; } /// <summary> @@ -254,6 +249,23 @@ namespace MediaBrowser.Providers.Movies } } + internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken) + { + var path = GetFanartXmlPath(tmdbId); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return Task.FromResult(true); + } + } + + return DownloadMovieXml(tmdbId, cancellationToken); + } + private async Task FetchImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs index c34bd47d7..7e0222f72 100644 --- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -41,7 +42,14 @@ namespace MediaBrowser.Providers.Movies public bool Supports(IHasImages item) { - return FanArtMovieProvider.SupportsItem(item); + var trailer = item as Trailer; + + if (trailer != null) + { + return !trailer.IsLocalTrailer; + } + + return item is Movie || item is BoxSet || item is MusicVideo; } public IEnumerable<ImageType> GetSupportedImages(IHasImages item) @@ -65,7 +73,7 @@ namespace MediaBrowser.Providers.Movies return images.Where(i => i.Type == imageType); } - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) { var baseItem = (BaseItem)item; var list = new List<RemoteImageInfo>(); @@ -74,6 +82,8 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(movieId)) { + await FanArtMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false); + var xmlPath = FanArtMovieProvider.Current.GetFanartXmlPath(movieId); try @@ -91,7 +101,7 @@ namespace MediaBrowser.Providers.Movies var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); // Sort first by width to prioritize HD versions - list = list.OrderByDescending(i => i.Width ?? 0) + return list.OrderByDescending(i => i.Width ?? 0) .ThenByDescending(i => { if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) @@ -111,10 +121,7 @@ namespace MediaBrowser.Providers.Movies } return 0; }) - .ThenByDescending(i => i.CommunityRating ?? 0) - .ToList(); - - return Task.FromResult<IEnumerable<RemoteImageInfo>>(list); + .ThenByDescending(i => i.CommunityRating ?? 0); } private void AddImages(List<RemoteImageInfo> list, string xmlPath, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs index ee1f14e09..c8cd746dc 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -18,13 +18,11 @@ namespace MediaBrowser.Providers.Movies class ManualMovieDbImageProvider : IRemoteImageProvider { private readonly IJsonSerializer _jsonSerializer; - private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; - public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IServerConfigurationManager config, IHttpClient httpClient) + public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) { _jsonSerializer = jsonSerializer; - _config = config; _httpClient = httpClient; } @@ -40,7 +38,15 @@ namespace MediaBrowser.Providers.Movies public bool Supports(IHasImages item) { - return MovieDbImagesProvider.SupportsItem(item); + var trailer = item as Trailer; + + if (trailer != null) + { + return !trailer.IsLocalTrailer; + } + + // Don't support local trailers + return item is Movie || item is BoxSet || item is MusicVideo; } public IEnumerable<ImageType> GetSupportedImages(IHasImages item) diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs index 7386f47f4..55d1b7588 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs @@ -58,11 +58,6 @@ namespace MediaBrowser.Providers.Movies /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return SupportsItem(item); - } - - internal static bool SupportsItem(IHasImages item) - { var trailer = item as Trailer; if (trailer != null) @@ -71,7 +66,7 @@ namespace MediaBrowser.Providers.Movies } // Don't support local trailers - return item is Movie || item is BoxSet || item is MusicVideo; + return item is Movie || item is MusicVideo; } public override ItemUpdateType ItemUpdateType diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 6f32aa135..d1cc17e15 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Providers.Movies } // Don't support local trailers - return item is Movie || item is BoxSet || item is MusicVideo; + return item is Movie || item is MusicVideo; } /// <summary> @@ -182,9 +182,7 @@ namespace MediaBrowser.Providers.Movies } private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}"; - private const string Search3 = @"http://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}"; private const string GetMovieInfo3 = @"http://api.themoviedb.org/3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers"; - private const string GetBoxSetInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; @@ -217,12 +215,11 @@ namespace MediaBrowser.Providers.Movies /// Gets the movie data path. /// </summary> /// <param name="appPaths">The app paths.</param> - /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param> /// <param name="tmdbId">The TMDB id.</param> /// <returns>System.String.</returns> - internal static string GetMovieDataPath(IApplicationPaths appPaths, bool isBoxSet, string tmdbId) + internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId) { - var dataPath = isBoxSet ? GetBoxSetsDataPath(appPaths) : GetMoviesDataPath(appPaths); + var dataPath = GetMoviesDataPath(appPaths); return Path.Combine(dataPath, tmdbId); } @@ -234,13 +231,6 @@ namespace MediaBrowser.Providers.Movies return dataPath; } - internal static string GetBoxSetsDataPath(IApplicationPaths appPaths) - { - var dataPath = Path.Combine(appPaths.DataPath, "tmdb-collections"); - - return dataPath; - } - /// <summary> /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// </summary> @@ -262,7 +252,8 @@ namespace MediaBrowser.Providers.Movies // Don't search for music video id's because it is very easy to misidentify. if (string.IsNullOrEmpty(id) && !(item is MusicVideo)) { - id = await FindId(item, cancellationToken).ConfigureAwait(false); + id = await new MovieDbSearch(Logger, JsonSerializer) + .FindMovieId(GetId(item), cancellationToken).ConfigureAwait(false); } if (!string.IsNullOrEmpty(id)) @@ -276,6 +267,17 @@ namespace MediaBrowser.Providers.Movies return true; } + private ItemId GetId(IHasMetadata item) + { + return new ItemId + { + MetadataCountryCode = item.GetPreferredMetadataCountryCode(), + MetadataLanguage = item.GetPreferredMetadataLanguage(), + Name = item.Name, + ProviderIds = item.ProviderIds + }; + } + /// <summary> /// Determines whether [has alt meta] [the specified item]. /// </summary> @@ -283,11 +285,6 @@ namespace MediaBrowser.Providers.Movies /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns> internal static bool HasAltMeta(BaseItem item) { - if (item is BoxSet) - { - return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("collection.xml"); - } - var path = MovieXmlSaver.GetMovieSavePath(item); if (item.LocationType == LocationType.FileSystem) @@ -299,191 +296,6 @@ namespace MediaBrowser.Providers.Movies return false; } - /// <summary> - /// Finds the id. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token</param> - /// <returns>Task{System.String}.</returns> - public async Task<string> FindId(BaseItem item, CancellationToken cancellationToken) - { - int? yearInName; - string name = item.Name; - NameParser.ParseName(name, out name, out yearInName); - - var year = item.ProductionYear ?? yearInName; - - Logger.Info("MovieDbProvider: Finding id for item: " + name); - var language = item.GetPreferredMetadataLanguage().ToLower(); - - //if we are a boxset - look at our first child - var boxset = item as BoxSet; - if (boxset != null) - { - // See if any movies have a collection id already - var collId = boxset.Children.Concat(boxset.GetLinkedChildren()).OfType<Video>() - .Select(i => i.GetProviderId(MetadataProviders.TmdbCollection)) - .FirstOrDefault(i => i != null); - - if (collId != null) return collId; - - } - - //nope - search for it - var searchType = item is BoxSet ? "collection" : "movie"; - var id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false); - if (id == null) - { - //try in english if wasn't before - if (language != "en") - { - id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false); - } - else - { - // try with dot and _ turned to space - var originalName = name; - - name = name.Replace(",", " "); - name = name.Replace(".", " "); - name = name.Replace("_", " "); - name = name.Replace("-", " "); - - // Search again if the new name is different - if (!string.Equals(name, originalName)) - { - id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false); - - if (id == null && language != "en") - { - //one more time, in english - id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false); - - } - } - - if (id == null && item.LocationType == LocationType.FileSystem) - { - //last resort - try using the actual folder name - var pathName = Path.GetFileName(item.ResolveArgs.Path); - - // Only search if it's a name we haven't already tried. - if (!string.Equals(pathName, name, StringComparison.OrdinalIgnoreCase) - && !string.Equals(pathName, originalName, StringComparison.OrdinalIgnoreCase)) - { - id = await AttemptFindId(pathName, searchType, year, "en", cancellationToken).ConfigureAwait(false); - } - } - } - } - - return id; - } - - /// <summary> - /// Attempts the find id. - /// </summary> - /// <param name="name">The name.</param> - /// <param name="type">movie or collection</param> - /// <param name="year">The year.</param> - /// <param name="language">The language.</param> - /// <param name="cancellationToken">The cancellation token</param> - /// <returns>Task{System.String}.</returns> - private async Task<string> AttemptFindId(string name, string type, int? year, string language, CancellationToken cancellationToken) - { - string url3 = string.Format(Search3, UrlEncode(name), ApiKey, language, type); - TmdbMovieSearchResults searchResult = null; - - using (Stream json = await GetMovieDbResponse(new HttpRequestOptions - { - Url = url3, - CancellationToken = cancellationToken, - AcceptHeader = AcceptHeader - - }).ConfigureAwait(false)) - { - searchResult = JsonSerializer.DeserializeFromStream<TmdbMovieSearchResults>(json); - } - - if (searchResult != null) - { - return FindIdOfBestResult(searchResult.results, name, year); - } - - return null; - } - - private string FindIdOfBestResult(List<TmdbMovieSearchResult> results, string name, int? year) - { - if (year.HasValue) - { - // Take the first result from the same year - var id = results.Where(i => - { - // Make sure it has a name - if (!string.IsNullOrEmpty(i.title ?? i.name)) - { - DateTime r; - - // These dates are always in this exact format - if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) - { - return r.Year == year.Value; - } - } - - return false; - }) - .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) - .FirstOrDefault(); - - if (!string.IsNullOrEmpty(id)) - { - return id; - } - - // Take the first result within one year - id = results.Where(i => - { - // Make sure it has a name - if (!string.IsNullOrEmpty(i.title ?? i.name)) - { - DateTime r; - - // These dates are always in this exact format - if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) - { - return Math.Abs(r.Year - year.Value) <= 1; - } - } - - return false; - }) - .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) - .FirstOrDefault(); - - if (!string.IsNullOrEmpty(id)) - { - return id; - } - } - - // Just take the first one - return results.Where(i => !string.IsNullOrEmpty(i.title ?? i.name)) - .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) - .FirstOrDefault(); - } - - /// <summary> - /// URLs the encode. - /// </summary> - /// <param name="name">The name.</param> - /// <returns>System.String.</returns> - private static string UrlEncode(string name) - { - return WebUtility.UrlEncode(name); - } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// <summary> @@ -499,23 +311,20 @@ namespace MediaBrowser.Providers.Movies // Id could be ImdbId or TmdbId var language = item.GetPreferredMetadataLanguage(); - var country = item.GetPreferredMetadataCountryCode(); var dataFilePath = GetDataFilePath(item); var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); - var isBoxSet = item is BoxSet; - if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath)) { - var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false); + var mainResult = await FetchMainResult(id, language, cancellationToken).ConfigureAwait(false); if (mainResult == null) return; tmdbId = mainResult.id.ToString(_usCulture); - dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language); + dataFilePath = GetDataFilePath(tmdbId, language); var directory = Path.GetDirectoryName(dataFilePath); @@ -526,7 +335,7 @@ namespace MediaBrowser.Providers.Movies if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item)) { - dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language); + dataFilePath = GetDataFilePath(tmdbId, language); if (!string.IsNullOrEmpty(dataFilePath)) { @@ -541,17 +350,16 @@ namespace MediaBrowser.Providers.Movies /// Downloads the movie info. /// </summary> /// <param name="id">The id.</param> - /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param> /// <param name="preferredMetadataLanguage">The preferred metadata language.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - internal async Task DownloadMovieInfo(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal async Task DownloadMovieInfo(string id, string preferredMetadataLanguage, CancellationToken cancellationToken) { - var mainResult = await FetchMainResult(id, isBoxSet, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + var mainResult = await FetchMainResult(id, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); if (mainResult == null) return; - var dataFilePath = GetDataFilePath(isBoxSet, id, preferredMetadataLanguage); + var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage); Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); @@ -567,7 +375,7 @@ namespace MediaBrowser.Providers.Movies if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if (ConfigurationManager.Configuration.EnableTmdbUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + if ((ConfigurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) { return Task.FromResult(true); } @@ -580,7 +388,7 @@ namespace MediaBrowser.Providers.Movies return Task.FromResult(true); } - return DownloadMovieInfo(id, item is BoxSet, item.GetPreferredMetadataLanguage(), cancellationToken); + return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken); } /// <summary> @@ -597,12 +405,12 @@ namespace MediaBrowser.Providers.Movies return null; } - return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage()); + return GetDataFilePath(id, item.GetPreferredMetadataLanguage()); } - private string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage) + private string GetDataFilePath(string tmdbId, string preferredLanguage) { - var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId); + var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId); var filename = string.Format("all-{0}.json", preferredLanguage ?? string.Empty); @@ -614,15 +422,12 @@ namespace MediaBrowser.Providers.Movies /// Fetches the main result. /// </summary> /// <param name="id">The id.</param> - /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param> /// <param name="language">The language.</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>Task{CompleteMovieData}.</returns> - private async Task<CompleteMovieData> FetchMainResult(string id, bool isBoxSet, string language, CancellationToken cancellationToken) + private async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken) { - var baseUrl = isBoxSet ? GetBoxSetInfo3 : GetMovieInfo3; - - var url = string.Format(baseUrl, id, ApiKey); + var url = string.Format(GetMovieInfo3, id, ApiKey); // Get images in english and with no language url += "&include_image_language=en,null"; @@ -661,7 +466,7 @@ namespace MediaBrowser.Providers.Movies { Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English..."); - url = string.Format(baseUrl, id, ApiKey, "en"); + url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en"; using (var json = await GetMovieDbResponse(new HttpRequestOptions { @@ -786,17 +591,6 @@ namespace MediaBrowser.Providers.Movies movie.ProductionYear = movieData.release_date.Year; } - // If that didn't find a rating and we are a boxset, use the one from our first child - if (movie.OfficialRating == null && movie is BoxSet && !movie.LockedFields.Contains(MetadataFields.OfficialRating)) - { - var boxset = movie as BoxSet; - Logger.Info("MovieDbProvider - Using rating of first child of boxset..."); - - var firstChild = boxset.Children.Concat(boxset.GetLinkedChildren()).FirstOrDefault(); - - boxset.OfficialRating = firstChild != null ? firstChild.OfficialRating : null; - } - //studios if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios)) { @@ -944,94 +738,6 @@ namespace MediaBrowser.Providers.Movies public List<TmdbTitle> titles { get; set; } } - /// <summary> - /// Class TmdbMovieSearchResult - /// </summary> - internal class TmdbMovieSearchResult - { - /// <summary> - /// Gets or sets a value indicating whether this <see cref="TmdbMovieSearchResult" /> is adult. - /// </summary> - /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value> - public bool adult { get; set; } - /// <summary> - /// Gets or sets the backdrop_path. - /// </summary> - /// <value>The backdrop_path.</value> - public string backdrop_path { get; set; } - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public int id { get; set; } - /// <summary> - /// Gets or sets the original_title. - /// </summary> - /// <value>The original_title.</value> - public string original_title { get; set; } - /// <summary> - /// Gets or sets the release_date. - /// </summary> - /// <value>The release_date.</value> - public string release_date { get; set; } - /// <summary> - /// Gets or sets the poster_path. - /// </summary> - /// <value>The poster_path.</value> - public string poster_path { get; set; } - /// <summary> - /// Gets or sets the popularity. - /// </summary> - /// <value>The popularity.</value> - public double popularity { get; set; } - /// <summary> - /// Gets or sets the title. - /// </summary> - /// <value>The title.</value> - public string title { get; set; } - /// <summary> - /// Gets or sets the vote_average. - /// </summary> - /// <value>The vote_average.</value> - public double vote_average { get; set; } - /// <summary> - /// For collection search results - /// </summary> - public string name { get; set; } - /// <summary> - /// Gets or sets the vote_count. - /// </summary> - /// <value>The vote_count.</value> - public int vote_count { get; set; } - } - - /// <summary> - /// Class TmdbMovieSearchResults - /// </summary> - internal class TmdbMovieSearchResults - { - /// <summary> - /// Gets or sets the page. - /// </summary> - /// <value>The page.</value> - public int page { get; set; } - /// <summary> - /// Gets or sets the results. - /// </summary> - /// <value>The results.</value> - public List<TmdbMovieSearchResult> results { get; set; } - /// <summary> - /// Gets or sets the total_pages. - /// </summary> - /// <value>The total_pages.</value> - public int total_pages { get; set; } - /// <summary> - /// Gets or sets the total_results. - /// </summary> - /// <value>The total_results.</value> - public int total_results { get; set; } - } - internal class BelongsToCollection { public int id { get; set; } diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs new file mode 100644 index 000000000..d4c6c6ff8 --- /dev/null +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -0,0 +1,261 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Movies +{ + public class MovieDbSearch + { + private static readonly CultureInfo EnUs = new CultureInfo("en-US"); + private const string Search3 = @"http://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}"; + + internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; + internal static string AcceptHeader = "application/json,image/*"; + + private readonly ILogger _logger; + private readonly IJsonSerializer _json; + + public MovieDbSearch(ILogger logger, IJsonSerializer json) + { + _logger = logger; + _json = json; + } + + public Task<string> FindMovieId(ItemId idInfo, CancellationToken cancellationToken) + { + return FindId(idInfo, "movie", cancellationToken); + } + + public Task<string> FindCollectionId(ItemId idInfo, CancellationToken cancellationToken) + { + return FindId(idInfo, "collection", cancellationToken); + } + + private async Task<string> FindId(ItemId idInfo, string searchType, CancellationToken cancellationToken) + { + int? yearInName; + var name = idInfo.Name; + NameParser.ParseName(name, out name, out yearInName); + + var year = idInfo.Year ?? yearInName; + + _logger.Info("MovieDbProvider: Finding id for item: " + name); + var language = idInfo.MetadataLanguage.ToLower(); + + //nope - search for it + //var searchType = item is BoxSet ? "collection" : "movie"; + + var id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false); + + if (id == null) + { + //try in english if wasn't before + if (language != "en") + { + id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false); + } + else + { + // try with dot and _ turned to space + var originalName = name; + + name = name.Replace(",", " "); + name = name.Replace(".", " "); + name = name.Replace("_", " "); + name = name.Replace("-", " "); + + // Search again if the new name is different + if (!string.Equals(name, originalName)) + { + id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false); + + if (id == null && language != "en") + { + //one more time, in english + id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false); + + } + } + } + } + + return id; + } + + private async Task<string> AttemptFindId(string name, string type, int? year, string language, CancellationToken cancellationToken) + { + var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, type); + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url3, + CancellationToken = cancellationToken, + AcceptHeader = AcceptHeader + + }).ConfigureAwait(false)) + { + var searchResult = _json.DeserializeFromStream<TmdbMovieSearchResults>(json); + return FindIdOfBestResult(searchResult.results, name, year); + } + } + + private string FindIdOfBestResult(List<TmdbMovieSearchResult> results, string name, int? year) + { + if (year.HasValue) + { + // Take the first result from the same year + var id = results.Where(i => + { + // Make sure it has a name + if (!string.IsNullOrEmpty(i.title ?? i.name)) + { + DateTime r; + + // These dates are always in this exact format + if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) + { + return r.Year == year.Value; + } + } + + return false; + }) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(id)) + { + return id; + } + + // Take the first result within one year + id = results.Where(i => + { + // Make sure it has a name + if (!string.IsNullOrEmpty(i.title ?? i.name)) + { + DateTime r; + + // These dates are always in this exact format + if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) + { + return Math.Abs(r.Year - year.Value) <= 1; + } + } + + return false; + }) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(id)) + { + return id; + } + } + + // Just take the first one + return results.Where(i => !string.IsNullOrEmpty(i.title ?? i.name)) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); + } + + + /// <summary> + /// Class TmdbMovieSearchResult + /// </summary> + private class TmdbMovieSearchResult + { + /// <summary> + /// Gets or sets a value indicating whether this <see cref="TmdbMovieSearchResult" /> is adult. + /// </summary> + /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value> + public bool adult { get; set; } + /// <summary> + /// Gets or sets the backdrop_path. + /// </summary> + /// <value>The backdrop_path.</value> + public string backdrop_path { get; set; } + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + public int id { get; set; } + /// <summary> + /// Gets or sets the original_title. + /// </summary> + /// <value>The original_title.</value> + public string original_title { get; set; } + /// <summary> + /// Gets or sets the release_date. + /// </summary> + /// <value>The release_date.</value> + public string release_date { get; set; } + /// <summary> + /// Gets or sets the poster_path. + /// </summary> + /// <value>The poster_path.</value> + public string poster_path { get; set; } + /// <summary> + /// Gets or sets the popularity. + /// </summary> + /// <value>The popularity.</value> + public double popularity { get; set; } + /// <summary> + /// Gets or sets the title. + /// </summary> + /// <value>The title.</value> + public string title { get; set; } + /// <summary> + /// Gets or sets the vote_average. + /// </summary> + /// <value>The vote_average.</value> + public double vote_average { get; set; } + /// <summary> + /// For collection search results + /// </summary> + public string name { get; set; } + /// <summary> + /// Gets or sets the vote_count. + /// </summary> + /// <value>The vote_count.</value> + public int vote_count { get; set; } + } + + /// <summary> + /// Class TmdbMovieSearchResults + /// </summary> + private class TmdbMovieSearchResults + { + /// <summary> + /// Gets or sets the page. + /// </summary> + /// <value>The page.</value> + public int page { get; set; } + /// <summary> + /// Gets or sets the results. + /// </summary> + /// <value>The results.</value> + public List<TmdbMovieSearchResult> results { get; set; } + /// <summary> + /// Gets or sets the total_pages. + /// </summary> + /// <value>The total_pages.</value> + public int total_pages { get; set; } + /// <summary> + /// Gets or sets the total_results. + /// </summary> + /// <value>The total_results.</value> + public int total_results { get; set; } + } + + } +} diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs index d9a367e2c..3f200dc8c 100644 --- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs @@ -75,29 +75,7 @@ namespace MediaBrowser.Providers.Movies return; } - var innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(pct * .8)); - await Run(innerProgress, false, cancellationToken).ConfigureAwait(false); - - progress.Report(80); - - //innerProgress = new ActionableProgress<double>(); - //innerProgress.RegisterAction(pct => progress.Report(80 + pct * .2)); - //await Run(innerProgress, true, cancellationToken).ConfigureAwait(false); - - progress.Report(100); - } - - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="runForBoxSets">if set to <c>true</c> [run for box sets].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task Run(IProgress<double> progress, bool runForBoxSets, CancellationToken cancellationToken) - { - var path = runForBoxSets ? MovieDbProvider.GetBoxSetsDataPath(_config.CommonApplicationPaths) : MovieDbProvider.GetMoviesDataPath(_config.CommonApplicationPaths); + var path = MovieDbProvider.GetMoviesDataPath(_config.CommonApplicationPaths); Directory.CreateDirectory(path); @@ -136,7 +114,7 @@ namespace MediaBrowser.Providers.Movies var idsToUpdate = updatedIds.Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i)); - await UpdateMovies(idsToUpdate, runForBoxSets, path, progress, cancellationToken).ConfigureAwait(false); + await UpdateMovies(idsToUpdate, progress, cancellationToken).ConfigureAwait(false); } } @@ -191,12 +169,10 @@ namespace MediaBrowser.Providers.Movies /// Updates the movies. /// </summary> /// <param name="ids">The ids.</param> - /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param> - /// <param name="moviesDataPath">The movies data path.</param> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task UpdateMovies(IEnumerable<string> ids, bool isBoxSet, string moviesDataPath, IProgress<double> progress, CancellationToken cancellationToken) + private async Task UpdateMovies(IEnumerable<string> ids, IProgress<double> progress, CancellationToken cancellationToken) { var list = ids.ToList(); var numComplete = 0; @@ -219,7 +195,7 @@ namespace MediaBrowser.Providers.Movies { try { - await UpdateMovie(id, isBoxSet, language, cancellationToken).ConfigureAwait(false); + await UpdateMovie(id, language, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -240,15 +216,14 @@ namespace MediaBrowser.Providers.Movies /// Updates the movie. /// </summary> /// <param name="id">The id.</param> - /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param> /// <param name="preferredMetadataLanguage">The preferred metadata language.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private Task UpdateMovie(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken) + private Task UpdateMovie(string id, string preferredMetadataLanguage, CancellationToken cancellationToken) { _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage); - return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, preferredMetadataLanguage, cancellationToken); + return MovieDbProvider.Current.DownloadMovieInfo(id, preferredMetadataLanguage, cancellationToken); } class Result diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index b88ca92bc..aba60e7ba 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.MusicGenres { - public class MusicGenreMetadataService : MetadataService<MusicGenre> + public class MusicGenreMetadataService : ConcreteMetadataService<MusicGenre> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs index 088ba0322..15a54474b 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs @@ -88,6 +88,7 @@ namespace MediaBrowser.Providers.People item.SetProviderId(MetadataProviders.Imdb, info.imdb_id); } + result.HasMetadata = true; result.Item = item; } diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index e04013934..fe17f67b1 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.People { - public class PersonMetadataService : MetadataService<Person> + public class PersonMetadataService : ConcreteMetadataService<Person> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index 1a35b94b3..f27a5c3b0 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Studios { - public class StudioMetadataService : MetadataService<Studio> + public class StudioMetadataService : ConcreteMetadataService<Studio> { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs new file mode 100644 index 000000000..f2b2da087 --- /dev/null +++ b/MediaBrowser.Providers/Users/UserMetadataService.cs @@ -0,0 +1,41 @@ +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.Users +{ + public class UserMetadataService : ConcreteMetadataService<User> + { + private readonly IUserManager _userManager; + + public UserMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager, IUserManager userManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) + { + _userManager = userManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + protected override void MergeData(User source, User target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(User item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _userManager.UpdateUser(item); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs index 7ae740508..0f7e94ac5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs @@ -18,9 +18,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer { var log = new StringBuilder(); - var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); + //var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); - log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); + //log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; @@ -43,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer log.AppendLine(string.Format("Url: {0}", url)); - log.AppendLine("Headers: " + string.Join(",", response.Headers.AllKeys.Select(k => k + "=" + response.Headers[k]))); + //log.AppendLine("Headers: " + string.Join(",", response.Headers.AllKeys.Select(k => k + "=" + response.Headers[k]))); var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds); diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index 91112b409..12686f542 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Library { var user = _userManager.GetUserById(new Guid(query.UserId)); - var inputItems = user.RootFolder.GetRecursiveChildren(user, null); + var inputItems = user.RootFolder.GetRecursiveChildren(user, null).Where(i => !(i is ICollectionFolder)); var results = await GetSearchHints(inputItems, query).ConfigureAwait(false); |
