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
-
+