From 945e84327087c9e81371c7b4f940a19a0c083586 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 9 Jun 2014 15:16:14 -0400 Subject: [PATCH] add new chapter provider feature --- MediaBrowser.Api/Library/ChapterService.cs | 28 ++ MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + .../Playback/BaseStreamingService.cs | 4 +- .../UserLibrary/UserLibraryService.cs | 2 +- .../Security/UsageReporter.cs | 6 +- .../Chapters/ChapterSearchRequest.cs | 2 + .../Chapters/IChapterManager.cs | 57 +++++ .../MediaBrowser.Controller.csproj | 1 + .../Providers/ICustomMetadataProvider.cs | 9 +- .../Chapters/RemoteChapterInfo.cs | 6 + .../Chapters/RemoteChapterResult.cs | 6 + .../Configuration/ServerConfiguration.cs | 34 ++- MediaBrowser.Model/LiveTv/ChannelInfoDto.cs | 4 + .../Chapters/ChapterManager.cs | 240 ++++++++++++++++++ .../Manager/MetadataService.cs | 8 +- .../MediaBrowser.Providers.csproj | 1 + .../MediaInfo/FFProbeProvider.cs | 50 ++-- .../MediaInfo/FFProbeVideoInfo.cs | 108 ++++++-- .../Photos/PhotoProvider.cs | 2 +- .../LiveTv/LiveTvManager.cs | 3 + .../Localization/JavaScript/javascript.json | 4 +- .../Localization/Server/server.json | 4 + .../MediaEncoder/EncodingManager.cs | 6 +- .../ApplicationHost.cs | 9 +- .../Api/DashboardService.cs | 1 + .../MediaBrowser.WebDashboard.csproj | 6 + Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 29 files changed, 536 insertions(+), 76 deletions(-) create mode 100644 MediaBrowser.Api/Library/ChapterService.cs create mode 100644 MediaBrowser.Controller/Chapters/IChapterManager.cs create mode 100644 MediaBrowser.Providers/Chapters/ChapterManager.cs diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs new file mode 100644 index 000000000..72ffa3fca --- /dev/null +++ b/MediaBrowser.Api/Library/ChapterService.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Chapters; +using ServiceStack; +using System.Linq; + +namespace MediaBrowser.Api.Library +{ + [Route("/Providers/Chapters", "GET")] + public class GetChapterProviders : IReturnVoid + { + } + + public class ChapterService : BaseApiService + { + private readonly IChapterManager _chapterManager; + + public ChapterService(IChapterManager chapterManager) + { + _chapterManager = chapterManager; + } + + public object Get(GetChapterProviders request) + { + var result = _chapterManager.GetProviders().ToList(); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 92bbb6130..1e9ff9199 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -68,6 +68,7 @@ + diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 6362f1084..c2b393fc6 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -363,11 +363,11 @@ namespace MediaBrowser.Api.Playback switch (qualitySetting) { case EncodingQuality.HighSpeed: - crf = "16"; + crf = "12"; profileScore = 2; break; case EncodingQuality.HighQuality: - crf = "10"; + crf = "8"; profileScore = 1; break; case EncodingQuality.MaxQuality: diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 28a365354..96bbd6dff 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -561,7 +561,7 @@ namespace MediaBrowser.Api.UserLibrary return dtos.ToList(); } - throw new ArgumentException("The item does not support special features"); + return new List(); } /// diff --git a/MediaBrowser.Common.Implementations/Security/UsageReporter.cs b/MediaBrowser.Common.Implementations/Security/UsageReporter.cs index 32e3a90de..6982d49fd 100644 --- a/MediaBrowser.Common.Implementations/Security/UsageReporter.cs +++ b/MediaBrowser.Common.Implementations/Security/UsageReporter.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Net; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -25,13 +26,16 @@ namespace MediaBrowser.Common.Implementations.Security var mac = _networkManager.GetMacAddress(); + var plugins = string.Join("|", _applicationHost.Plugins.Select(i => i.Name).ToArray()); + var data = new Dictionary { { "feature", _applicationHost.Name }, { "mac", mac }, { "ver", _applicationHost.ApplicationVersion.ToString() }, { "platform", Environment.OSVersion.VersionString }, - { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()} + { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()}, + { "plugins", plugins} }; return _httpClient.Post(Constants.Constants.MbAdminUrl + "service/registration/ping", data, cancellationToken); diff --git a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs index 9a53d68ea..982dc35bb 100644 --- a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs +++ b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs @@ -21,6 +21,8 @@ namespace MediaBrowser.Controller.Chapters public long? RuntimeTicks { get; set; } public Dictionary ProviderIds { get; set; } + public bool SearchAllProviders { get; set; } + public ChapterSearchRequest() { ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs new file mode 100644 index 000000000..df230bf7e --- /dev/null +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -0,0 +1,57 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Chapters; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Chapters +{ + /// + /// Interface IChapterManager + /// + public interface IChapterManager + { + /// + /// Adds the parts. + /// + /// The chapter providers. + void AddParts(IEnumerable chapterProviders); + + /// + /// Searches the specified video. + /// + /// The video. + /// The cancellation token. + /// Task{IEnumerable{RemoteChapterResult}}. + Task> Search(Video video, CancellationToken cancellationToken); + + /// + /// Searches the specified request. + /// + /// The request. + /// The cancellation token. + /// Task{IEnumerable{RemoteChapterResult}}. + Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken); + + /// + /// Gets the chapters. + /// + /// The identifier. + /// The cancellation token. + /// Task{ChapterResponse}. + Task GetChapters(string id, CancellationToken cancellationToken); + + /// + /// Gets the providers. + /// + /// The item identifier. + /// IEnumerable{ChapterProviderInfo}. + IEnumerable GetProviders(string itemId); + + /// + /// Gets the providers. + /// + /// IEnumerable{ChapterProviderInfo}. + IEnumerable GetProviders(); + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 09bf4b470..05e8ba2fb 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -91,6 +91,7 @@ + diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs index 9aed4d921..3ce6ac46b 100644 --- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs @@ -12,7 +12,14 @@ namespace MediaBrowser.Controller.Providers public interface ICustomMetadataProvider : IMetadataProvider, ICustomMetadataProvider where TItemType : IHasMetadata { - Task FetchAsync(TItemType item, IDirectoryService directoryService, CancellationToken cancellationToken); + /// + /// Fetches the asynchronous. + /// + /// The item. + /// The options. + /// The cancellation token. + /// Task{ItemUpdateType}. + Task FetchAsync(TItemType item, MetadataRefreshOptions options, CancellationToken cancellationToken); } public interface IPreRefreshProvider : ICustomMetadataProvider diff --git a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs b/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs index f2674c842..52b6c1bcd 100644 --- a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs +++ b/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs @@ -15,4 +15,10 @@ namespace MediaBrowser.Model.Chapters /// The name. public string Name { get; set; } } + + public class ChapterProviderInfo + { + public string Name { get; set; } + public string Id { get; set; } + } } diff --git a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs b/MediaBrowser.Model/Chapters/RemoteChapterResult.cs index 3f627c8df..425c3ded8 100644 --- a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs +++ b/MediaBrowser.Model/Chapters/RemoteChapterResult.cs @@ -21,6 +21,12 @@ namespace MediaBrowser.Model.Chapters /// The name. public string Name { get; set; } + /// + /// Gets or sets the name of the provider. + /// + /// The name of the provider. + public string ProviderName { get; set; } + /// /// Gets or sets the community rating. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 34707ecd7..bbe862238 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -193,10 +193,6 @@ namespace MediaBrowser.Model.Configuration public bool AllowVideoUpscaling { get; set; } - public bool EnableMovieChapterImageExtraction { get; set; } - public bool EnableEpisodeChapterImageExtraction { get; set; } - public bool EnableOtherVideoChapterImageExtraction { get; set; } - public MetadataOptions[] MetadataOptions { get; set; } public bool EnableDebugEncodingLogging { get; set; } @@ -227,6 +223,7 @@ namespace MediaBrowser.Model.Configuration public string[] ManualLoginClients { get; set; } public ChannelOptions ChannelOptions { get; set; } + public ChapterOptions ChapterOptions { get; set; } /// /// Initializes a new instance of the class. @@ -241,9 +238,6 @@ namespace MediaBrowser.Model.Configuration EnableHttpLevelLogging = true; EnableDashboardResponseCaching = true; - EnableMovieChapterImageExtraction = true; - EnableEpisodeChapterImageExtraction = false; - EnableOtherVideoChapterImageExtraction = false; EnableAutomaticRestart = true; EnablePeoplePrefixSubFolders = true; @@ -297,6 +291,7 @@ namespace MediaBrowser.Model.Configuration SubtitleOptions = new SubtitleOptions(); ChannelOptions = new ChannelOptions(); + ChapterOptions = new ChapterOptions(); } } @@ -315,4 +310,29 @@ namespace MediaBrowser.Model.Configuration MaxDownloadAge = 30; } } + + public class ChapterOptions + { + public bool EnableMovieChapterImageExtraction { get; set; } + public bool EnableEpisodeChapterImageExtraction { get; set; } + public bool EnableOtherVideoChapterImageExtraction { get; set; } + + public bool DownloadMovieChapters { get; set; } + public bool DownloadEpisodeChapters { get; set; } + + public string[] FetcherOrder { get; set; } + public string[] DisabledFetchers { get; set; } + + public ChapterOptions() + { + EnableMovieChapterImageExtraction = true; + EnableEpisodeChapterImageExtraction = false; + EnableOtherVideoChapterImageExtraction = false; + + DownloadMovieChapters = true; + + DisabledFetchers = new string[] { }; + FetcherOrder = new string[] { }; + } + } } diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs index 2f244b4e3..a48006755 100644 --- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs @@ -32,6 +32,10 @@ namespace MediaBrowser.Model.LiveTv /// The external identifier. public string ExternalId { get; set; } + /// + /// Gets or sets the media sources. + /// + /// The media sources. public List MediaSources { get; set; } /// diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs new file mode 100644 index 000000000..2723d80a2 --- /dev/null +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -0,0 +1,240 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Chapters; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Chapters +{ + public class ChapterManager : IChapterManager + { + private IChapterProvider[] _providers; + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + + public ChapterManager(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config) + { + _libraryManager = libraryManager; + _logger = logger; + _config = config; + } + + public void AddParts(IEnumerable chapterProviders) + { + _providers = chapterProviders.ToArray(); + } + + public Task> Search(Video video, CancellationToken cancellationToken) + { + VideoContentType mediaType; + + if (video is Episode) + { + mediaType = VideoContentType.Episode; + } + else if (video is Movie) + { + mediaType = VideoContentType.Movie; + } + else + { + // These are the only supported types + return Task.FromResult>(new List()); + } + + var request = new ChapterSearchRequest + { + ContentType = mediaType, + IndexNumber = video.IndexNumber, + //Language = language, + MediaPath = video.Path, + Name = video.Name, + ParentIndexNumber = video.ParentIndexNumber, + ProductionYear = video.ProductionYear, + ProviderIds = video.ProviderIds, + RuntimeTicks = video.RunTimeTicks + }; + + var episode = video as Episode; + + if (episode != null) + { + request.IndexNumberEnd = episode.IndexNumberEnd; + request.SeriesName = episode.SeriesName; + } + + return Search(request, cancellationToken); + } + + public async Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken) + { + var contentType = request.ContentType; + var providers = GetInternalProviders(false) + .Where(i => i.SupportedMediaTypes.Contains(contentType)) + .ToList(); + + // If not searching all, search one at a time until something is found + if (!request.SearchAllProviders) + { + foreach (var provider in providers) + { + try + { + return await Search(request, provider, cancellationToken).ConfigureAwait(false); + + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name); + } + } + return new List(); + } + + var tasks = providers.Select(async i => + { + try + { + return await Search(request, i, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading subtitles from {0}", ex, i.Name); + return new List(); + } + }); + + var results = await Task.WhenAll(tasks).ConfigureAwait(false); + + return results.SelectMany(i => i); + } + + private async Task> Search(ChapterSearchRequest request, + IChapterProvider provider, + CancellationToken cancellationToken) + { + var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false); + + foreach (var result in searchResults) + { + result.Id = GetProviderId(provider.Name) + "_" + result.Id; + result.ProviderName = provider.Name; + } + + return searchResults; + } + + public Task GetChapters(string id, CancellationToken cancellationToken) + { + var parts = id.Split(new[] { '_' }, 2); + + var provider = GetProvider(parts.First()); + id = parts.Last(); + + return provider.GetChapters(id, cancellationToken); + } + + public IEnumerable GetProviders(string itemId) + { + var video = _libraryManager.GetItemById(itemId) as Video; + VideoContentType mediaType; + + if (video is Episode) + { + mediaType = VideoContentType.Episode; + } + else if (video is Movie) + { + mediaType = VideoContentType.Movie; + } + else + { + // These are the only supported types + return new List(); + } + + var providers = GetInternalProviders(false) + .Where(i => i.SupportedMediaTypes.Contains(mediaType)); + + return GetInfos(providers); + } + + public IEnumerable GetProviders() + { + return GetInfos(GetInternalProviders(true)); + } + + private IEnumerable GetInternalProviders(bool includeDisabledProviders) + { + var providers = _providers; + + if (!includeDisabledProviders) + { + providers = providers + .Where(i => _config.Configuration.ChapterOptions.DisabledFetchers.Contains(i.Name)) + .ToArray(); + } + + return providers + .OrderBy(GetConfiguredOrder) + .ThenBy(GetDefaultOrder) + .ToArray(); + } + + private IEnumerable GetInfos(IEnumerable providers) + { + return providers.Select(i => new ChapterProviderInfo + { + Name = i.Name, + Id = GetProviderId(i.Name) + }); + } + + private string GetProviderId(string name) + { + return name.ToLower().GetMD5().ToString("N"); + } + + private IChapterProvider GetProvider(string id) + { + return _providers.First(i => string.Equals(id, GetProviderId(i.Name))); + } + + private int GetConfiguredOrder(IChapterProvider provider) + { + // See if there's a user-defined order + var index = Array.IndexOf(_config.Configuration.ChapterOptions.FetcherOrder, provider.Name); + + if (index != -1) + { + return index; + } + + // Not configured. Just return some high number to put it at the end. + return 100; + } + + private int GetDefaultOrder(IChapterProvider provider) + { + var hasOrder = provider as IHasOrder; + + if (hasOrder != null) + { + return hasOrder.Order; + } + + return 0; + } + } +} diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 1f58000fe..e40f49acf 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -271,7 +271,7 @@ namespace MediaBrowser.Providers.Manager foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider)) { - await RunCustomProvider(provider, item, options.DirectoryService, refreshResult, cancellationToken).ConfigureAwait(false); + await RunCustomProvider(provider, item, options, refreshResult, cancellationToken).ConfigureAwait(false); } var temp = CreateNew(); @@ -343,19 +343,19 @@ namespace MediaBrowser.Providers.Manager foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider))) { - await RunCustomProvider(provider, item, options.DirectoryService, refreshResult, cancellationToken).ConfigureAwait(false); + await RunCustomProvider(provider, item, options, refreshResult, cancellationToken).ConfigureAwait(false); } return refreshResult; } - private async Task RunCustomProvider(ICustomMetadataProvider provider, TItemType item, IDirectoryService directoryService, RefreshResult refreshResult, CancellationToken cancellationToken) + private async Task RunCustomProvider(ICustomMetadataProvider provider, TItemType item, MetadataRefreshOptions options, 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, directoryService, cancellationToken).ConfigureAwait(false); + refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index ce5f8aa28..3ad4a608a 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -79,6 +79,7 @@ + diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index bbbcaeedf..9e9936cab 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -49,58 +50,59 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; + private readonly IChapterManager _chapterManager; public string Name { get { return "ffprobe"; } } - public Task FetchAsync(Episode item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(MusicVideo item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(Movie item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(AdultVideo item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(AdultVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(LiveTvVideoRecording item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(LiveTvVideoRecording item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(Trailer item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(Video item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken) { - return FetchVideoInfo(item, directoryService, cancellationToken); + return FetchVideoInfo(item, options, cancellationToken); } - public Task FetchAsync(Audio item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return FetchAudioInfo(item, cancellationToken); } - public Task FetchAsync(LiveTvAudioRecording item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(LiveTvAudioRecording item, MetadataRefreshOptions options, CancellationToken cancellationToken) { return FetchAudioInfo(item, cancellationToken); } - public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager) + public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager) { _logger = logger; _isoManager = isoManager; @@ -114,10 +116,11 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; _config = config; _subtitleManager = subtitleManager; + _chapterManager = chapterManager; } private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); - public Task FetchVideoInfo(T item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchVideoInfo(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Video { if (item.LocationType != LocationType.FileSystem) @@ -140,9 +143,9 @@ namespace MediaBrowser.Providers.MediaInfo return _cachedTask; } - var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager); + var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager); - return prober.ProbeVideo(item, directoryService, true, cancellationToken); + return prober.ProbeVideo(item, options, cancellationToken); } public Task FetchAudioInfo(T item, CancellationToken cancellationToken) @@ -171,9 +174,10 @@ namespace MediaBrowser.Providers.MediaInfo if (video != null && !video.IsPlaceHolder) { - var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager); - - return !video.SubtitleFiles.SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, false).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase); + return !video.SubtitleFiles + .SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, false) + .Select(i => i.FullName) + .OrderBy(i => i), StringComparer.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 1cc04ed2a..61610a5f1 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -2,6 +2,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -41,10 +42,11 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; + private readonly IChapterManager _chapterManager; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager) + public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager) { _logger = logger; _isoManager = isoManager; @@ -58,9 +60,12 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; _config = config; _subtitleManager = subtitleManager; + _chapterManager = chapterManager; } - public async Task ProbeVideo(T item, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken) + public async Task ProbeVideo(T item, + MetadataRefreshOptions options, + CancellationToken cancellationToken) where T : Video { var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); @@ -105,7 +110,7 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService, enableSubtitleDownloading).ConfigureAwait(false); + await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false); } finally @@ -121,7 +126,9 @@ namespace MediaBrowser.Providers.MediaInfo private const string SchemaVersion = "1"; - private async Task GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken) + private async Task GetMediaInfo(BaseItem item, + IIsoMount isoMount, + CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -160,7 +167,12 @@ namespace MediaBrowser.Providers.MediaInfo return result; } - protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService, bool enableSubtitleDownloading) + protected async Task Fetch(Video video, + CancellationToken cancellationToken, + InternalMediaInfoResult data, + IIsoMount isoMount, + BlurayDiscInfo blurayInfo, + MetadataRefreshOptions options) { var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data); var mediaStreams = mediaInfo.MediaStreams; @@ -208,17 +220,12 @@ namespace MediaBrowser.Providers.MediaInfo FetchBdInfo(video, chapters, mediaStreams, blurayInfo); } - await AddExternalSubtitles(video, mediaStreams, directoryService, enableSubtitleDownloading, cancellationToken).ConfigureAwait(false); + await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); 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; @@ -228,18 +235,34 @@ namespace MediaBrowser.Providers.MediaInfo ExtractTimestamp(video); - await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions - { - Chapters = chapters, - Video = video, - ExtractImages = false, - SaveChapters = false - - }, cancellationToken).ConfigureAwait(false); - await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); - await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || + options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata) + { + var remoteChapters = await DownloadChapters(video, cancellationToken).ConfigureAwait(false); + + if (remoteChapters.Count > 0) + { + chapters = remoteChapters; + } + + if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) + { + AddDummyChapters(video, chapters); + } + + await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions + { + Chapters = chapters, + Video = video, + ExtractImages = false, + SaveChapters = false + + }, cancellationToken).ConfigureAwait(false); + + await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + } } private ChapterInfo GetChapterInfo(MediaChapter chapter) @@ -416,15 +439,20 @@ namespace MediaBrowser.Providers.MediaInfo /// /// The video. /// The current streams. - /// The directory service. - /// if set to true [enable subtitle downloading]. + /// The options. /// The cancellation token. /// Task. - private async Task AddExternalSubtitles(Video video, List currentStreams, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken) + private async Task AddExternalSubtitles(Video video, + List currentStreams, + MetadataRefreshOptions options, + CancellationToken cancellationToken) { var subtitleResolver = new SubtitleResolver(_localization); - var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList(); + var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, false).ToList(); + + var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata || + options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && video is Episode) || @@ -444,7 +472,7 @@ namespace MediaBrowser.Providers.MediaInfo // Rescan if (downloadedLanguages.Count > 0) { - externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, true).ToList(); + externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, true).ToList(); } } @@ -453,6 +481,33 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } + private async Task> DownloadChapters(Video video, CancellationToken cancellationToken) + { + if ((_config.Configuration.ChapterOptions.DownloadEpisodeChapters && + video is Episode) || + (_config.Configuration.ChapterOptions.DownloadMovieChapters && + video is Movie)) + { + var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false); + + var result = results.FirstOrDefault(); + + if (result != null) + { + var chapters = await _chapterManager.GetChapters(result.Id, cancellationToken).ConfigureAwait(false); + + return chapters.Chapters.Select(i => new ChapterInfo + { + Name = i.Name, + StartPositionTicks = i.StartPositionTicks + + }).ToList(); + } + } + + return new List(); + } + /// /// The dummy chapter duration /// @@ -499,6 +554,7 @@ namespace MediaBrowser.Providers.MediaInfo /// /// The item. /// The mount. + /// The bluray disc information. private void OnPreFetch(Video item, IIsoMount mount, BlurayDiscInfo blurayDiscInfo) { if (item.VideoType == VideoType.Iso) diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs index a89919d3f..c0f53e0c3 100644 --- a/MediaBrowser.Providers/Photos/PhotoProvider.cs +++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Photos _imageProcessor = imageProcessor; } - public Task FetchAsync(Photo item, IDirectoryService directoryService, CancellationToken cancellationToken) + public Task FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { item.SetImagePath(ImageType.Primary, item.Path); item.SetImagePath(ImageType.Backdrop, item.Path); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 9f7a3c8e5..625dd7e82 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -410,6 +410,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { stream.PacketLength = null; } + + // Don't trust the provider values + stream.Index = -1; } } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 9e51a40b0..52dd8209a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -143,5 +143,7 @@ "HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable.", "OptionNewCollection": "New...", "ButtonAdd": "Add", - "ButtonRemove": "Remove" + "ButtonRemove": "Remove", + "LabelChapterDownloaders": "Chapter downloaders:", + "LabelChapterDownloadersHelp": "Enable and rank your preferred chapter downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index b886d4f6b..74ac9aeeb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -733,11 +733,15 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.", "HeaderSubtitleDownloadingHelp": "When Media Browser scans your video files it can search for missing subtitles, and download them using a subtitle provider such as OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "Download subtitles for:", + "MessageNoChapterProviders": "Install a chapter provider plugin such as ChapterDb or tagChimp to enable additional chapter metadata options.", "LabelSkipIfGraphicalSubsPresent": "Skip if the video already contains graphical subtitles", "LabelSkipIfGraphicalSubsPresentHelp": "Keeping text versions of subtitles will result in more efficient delivery to mobile clients.", "TabSubtitles": "Subtitles", + "TabChapters": "Chapters", + "HeaderDownloadChaptersFor": "Download chapter names for:", "LabelOpenSubtitlesUsername": "Open Subtitles username:", "LabelOpenSubtitlesPassword": "Open Subtitles password:", + "HeaderChapterDownloadingHelp": "When Media Browser scans your video files it can download friendly chapter names from the internet using chapter plugins such as ChapterDb and tagChimp.", "LabelPlayDefaultAudioTrack": "Play default audio track regardless of language", "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelDownloadLanguages": "Download languages:", diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 73ab50ef6..ee3e15475 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -91,21 +91,21 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder if (video is Movie) { - if (!_config.Configuration.EnableMovieChapterImageExtraction) + if (!_config.Configuration.ChapterOptions.EnableMovieChapterImageExtraction) { return false; } } else if (video is Episode) { - if (!_config.Configuration.EnableEpisodeChapterImageExtraction) + if (!_config.Configuration.ChapterOptions.EnableEpisodeChapterImageExtraction) { return false; } } else { - if (!_config.Configuration.EnableOtherVideoChapterImageExtraction) + if (!_config.Configuration.ChapterOptions.EnableOtherVideoChapterImageExtraction) { return false; } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e754b7d9d..c4d6bce4d 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -10,6 +10,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -43,6 +44,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; +using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; using MediaBrowser.Server.Implementations; @@ -196,6 +198,7 @@ namespace MediaBrowser.ServerApplication private INotificationManager NotificationManager { get; set; } private ISubtitleManager SubtitleManager { get; set; } + private IChapterManager ChapterManager { get; set; } private IUserViewManager UserViewManager { get; set; } @@ -544,6 +547,9 @@ namespace MediaBrowser.ServerApplication SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, ItemRepository); RegisterSingleInstance(SubtitleManager); + ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager); + RegisterSingleInstance(ChapterManager); + var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false)); var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false)); @@ -725,7 +731,8 @@ namespace MediaBrowser.ServerApplication LiveTvManager.AddParts(GetExports()); SubtitleManager.AddParts(GetExports()); - + ChapterManager.AddParts(GetExports()); + SessionManager.AddParts(GetExports()); ChannelManager.AddParts(GetExports(), GetExports()); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 7946e7319..78344965d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -591,6 +591,7 @@ namespace MediaBrowser.WebDashboard.Api "metadataconfigurationpage.js", "metadataimagespage.js", "metadatasubtitles.js", + "metadatachapters.js", "moviegenres.js", "moviecollections.js", "movies.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index d8fa2c9be..709340dc0 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -331,6 +331,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -670,6 +673,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 5137daf23..d1e669b41 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.399 + 3.0.400 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index b78d6549b..873c308ed 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.399 + 3.0.400 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index cc710265a..e97a0c7d8 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.399 + 3.0.400 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +