From 59dc591f66c20b6417aa2baa9503a154585386f9 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 2 Jul 2014 14:34:08 -0400 Subject: update to jquery mobile 1.4.3 --- MediaBrowser.Controller/Library/IUserManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 010caa2337..0da5f92728 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -50,11 +50,11 @@ namespace MediaBrowser.Controller.Library /// /// Authenticates a User and returns a result indicating whether or not it succeeded /// - /// The user. + /// The username. /// The password. /// Task{System.Boolean}. /// user - Task AuthenticateUser(User user, string password); + Task AuthenticateUser(string username, string password); /// /// Refreshes metadata for each user -- cgit v1.2.3 From 1a371276dc92e303eba8445b2ba9c97766a052b2 Mon Sep 17 00:00:00 2001 From: Luis Miguel Almánzar Date: Thu, 3 Jul 2014 00:01:36 -0400 Subject: add multi-episode support for filenames without seasons --- MediaBrowser.Controller/Library/TVUtils.cs | 14 ++++++++++---- MediaBrowser.Tests/Resolvers/TvUtilTests.cs | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 64f3a3b4b7..0681a8cd1a 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -98,19 +98,19 @@ namespace MediaBrowser.Controller.Library private static readonly Regex[] EpisodeExpressionsWithoutSeason = { new Regex( - @".*[\\\/](?\d{1,3})\.\w+$", + @".*[\\\/](?\d{1,3})(-(?\d{2,3}))*\.\w+$", RegexOptions.Compiled), // "01.avi" new Regex( - @".*(\\|\/)(?\d{1,2})\s?-\s?[^\\\/]*$", + @".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$", RegexOptions.Compiled), // "01 - blah.avi", "01-blah.avi" new Regex( - @".*(\\|\/)(?\d{1,2})\.[^\\\/]+$", + @".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\.[^\\\/]+$", RegexOptions.Compiled), // "01.blah.avi" new Regex( - @".*[\\\/][^\\\/]* - (?\d{1,3})[^\\\/]*$", + @".*[\\\/][^\\\/]* - (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$", RegexOptions.Compiled), // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" }; @@ -284,6 +284,12 @@ namespace MediaBrowser.Controller.Library if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value)) return ParseEpisodeNumber(m.Groups["endingepnumber"].Value); } + foreach (var r in EpisodeExpressionsWithoutSeason) + { + var m = r.Match(fl); + if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value)) + return ParseEpisodeNumber(m.Groups["endingepnumber"].Value); + } return null; } diff --git a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs index 97e7907766..bffd22f3ed 100644 --- a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs +++ b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs @@ -31,6 +31,7 @@ namespace MediaBrowser.Tests.Resolvers Assert.AreEqual(23, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4", true)); Assert.AreEqual(9, TVUtils.GetEpisodeNumberFromFile(@"Season 25\The Simpsons.S25E09.Steal this episode.mp4", true)); Assert.AreEqual(8, TVUtils.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons.S25E08.Steal this episode.mp4", false)); + Assert.AreEqual(136, TVUtils.GetEpisodeNumberFromFile(@"Season 2\[HorribleSubs] Hunter X Hunter - 136 [720p].mkv",true)); //Four Digits seasons Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 2009\2009x02 blah.avi", true)); @@ -128,6 +129,13 @@ namespace MediaBrowser.Tests.Resolvers Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02 - blah 14 blah.avi")); Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\02 - blah-02 a.avi")); Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02.avi")); + + Assert.AreEqual(3, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\02-03 - blah.avi")); + Assert.AreEqual(4, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02-04 - blah 14 blah.avi")); + Assert.AreEqual(5, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\02-05 - blah-02 a.avi")); + Assert.AreEqual(4, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02-04.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); + } [TestMethod] -- cgit v1.2.3 From 7fa9b14f56eabbb06e38726879b3cddc47b8e8fb Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Jul 2014 22:22:57 -0400 Subject: fixes #762 - Marking unwatched doesn't update display --- MediaBrowser.Api/BrandingService.cs | 28 +++ MediaBrowser.Api/ConfigurationService.cs | 7 + MediaBrowser.Api/Images/ImageService.cs | 7 + MediaBrowser.Api/ItemRefreshService.cs | 22 ++- MediaBrowser.Api/Library/LibraryService.cs | 2 + MediaBrowser.Api/Library/SubtitleService.cs | 184 -------------------- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 +- MediaBrowser.Api/NotificationsService.cs | 2 + MediaBrowser.Api/SessionsService.cs | 12 +- MediaBrowser.Api/Subtitles/SubtitleService.cs | 189 +++++++++++++++++++++ MediaBrowser.Api/SystemService.cs | 70 +++++++- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 10 +- MediaBrowser.Api/UserService.cs | 4 + .../WebSocket/LogFileWebSocketListener.cs | 149 ---------------- MediaBrowser.Common/Net/MimeTypes.cs | 4 + MediaBrowser.Controller/Dto/IDtoService.cs | 7 - MediaBrowser.Controller/Entities/BaseItem.cs | 15 ++ MediaBrowser.Controller/Entities/Folder.cs | 80 ++++++++- MediaBrowser.Controller/Entities/IHasUserData.cs | 18 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 2 +- MediaBrowser.Controller/Entities/UserRootFolder.cs | 6 + MediaBrowser.Controller/Entities/UserView.cs | 2 +- .../Library/IUserDataManager.cs | 9 + MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs | 4 +- .../Providers/MetadataRefreshOptions.cs | 39 +++-- MediaBrowser.Controller/Session/ISessionManager.cs | 8 +- .../MediaBrowser.Model.Portable.csproj | 6 + .../MediaBrowser.Model.net35.csproj | 6 + MediaBrowser.Model/Branding/BrandingOptions.cs | 12 ++ .../Configuration/ServerConfiguration.cs | 3 - .../Configuration/XbmcMetadataOptions.cs | 3 + MediaBrowser.Model/Dto/UserItemDataDto.cs | 16 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + .../Notifications/NotificationRequest.cs | 3 +- MediaBrowser.Model/Session/SessionInfoDto.cs | 32 +--- MediaBrowser.Model/System/LogFile.cs | 31 ++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 28 ++- .../MediaInfo/FFProbeVideoInfo.cs | 4 +- .../Branding/BrandingConfigurationFactory.cs | 21 +++ .../Dto/DtoService.cs | 52 +++--- .../EntryPoints/UserDataChangeNotifier.cs | 45 +++-- .../HttpServer/HttpListenerHost.cs | 26 +-- .../HttpServer/Security/AuthorizationContext.cs | 9 +- .../Library/UserDataManager.cs | 37 ++++ .../LiveTv/LiveTvDtoService.cs | 11 +- .../LiveTv/LiveTvManager.cs | 6 +- .../Localization/Server/server.json | 17 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Notifications/NotificationManager.cs | 5 +- .../Session/SessionManager.cs | 27 ++- .../Sorting/AirTimeComparer.cs | 33 +++- MediaBrowser.ServerApplication/ApplicationHost.cs | 2 +- .../Native/BrowserLauncher.cs | 12 -- MediaBrowser.ServerApplication/ServerNotifyIcon.cs | 15 -- MediaBrowser.WebDashboard/Api/DashboardService.cs | 3 +- .../MediaBrowser.WebDashboard.csproj | 22 +-- 56 files changed, 821 insertions(+), 553 deletions(-) create mode 100644 MediaBrowser.Api/BrandingService.cs delete mode 100644 MediaBrowser.Api/Library/SubtitleService.cs create mode 100644 MediaBrowser.Api/Subtitles/SubtitleService.cs delete mode 100644 MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs create mode 100644 MediaBrowser.Model/Branding/BrandingOptions.cs create mode 100644 MediaBrowser.Model/System/LogFile.cs create mode 100644 MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs new file mode 100644 index 0000000000..4b49b411a8 --- /dev/null +++ b/MediaBrowser.Api/BrandingService.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Branding; +using ServiceStack; + +namespace MediaBrowser.Api +{ + [Route("/Branding/Configuration", "GET", Summary = "Gets branding configuration")] + public class GetBrandingOptions : IReturn + { + } + + public class BrandingService : BaseApiService + { + private readonly IConfigurationManager _config; + + public BrandingService(IConfigurationManager config) + { + _config = config; + } + + public object Get(GetBrandingOptions request) + { + var result = _config.GetConfiguration("branding"); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 39fcc50d8f..291deb3b01 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; @@ -27,6 +28,7 @@ namespace MediaBrowser.Api } [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] + [Authenticated] public class GetNamedConfiguration { [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -37,11 +39,13 @@ namespace MediaBrowser.Api /// Class UpdateConfiguration /// [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] + [Authenticated] public class UpdateConfiguration : ServerConfiguration, IReturnVoid { } [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")] + [Authenticated] public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream { [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -51,18 +55,21 @@ namespace MediaBrowser.Api } [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] + [Authenticated] public class GetDefaultMetadataOptions : IReturn { } [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")] + [Authenticated] public class GetMetadataPlugins : IReturn> { } [Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")] + [Authenticated] public class UpdateVideoImageExtraction : IReturnVoid { public bool Enabled { get; set; } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 4f8c348bb8..deaefe019a 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; @@ -26,6 +27,7 @@ namespace MediaBrowser.Api.Images /// [Route("/Items/{Id}/Images", "GET")] [Api(Description = "Gets information about an item's images")] + [Authenticated] public class GetItemImageInfos : IReturn> { /// @@ -56,6 +58,7 @@ namespace MediaBrowser.Api.Images /// [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")] [Api(Description = "Updates the index for an item image")] + [Authenticated] public class UpdateItemImageIndex : IReturnVoid { /// @@ -137,6 +140,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "DELETE")] [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")] [Api(Description = "Deletes an item image")] + [Authenticated] public class DeleteItemImage : DeleteImageRequest, IReturnVoid { /// @@ -153,6 +157,7 @@ namespace MediaBrowser.Api.Images [Route("/Users/{Id}/Images/{Type}", "DELETE")] [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] [Api(Description = "Deletes a user image")] + [Authenticated] public class DeleteUserImage : DeleteImageRequest, IReturnVoid { /// @@ -169,6 +174,7 @@ namespace MediaBrowser.Api.Images [Route("/Users/{Id}/Images/{Type}", "POST")] [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] [Api(Description = "Posts a user image")] + [Authenticated] public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// @@ -191,6 +197,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "POST")] [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")] [Api(Description = "Posts an item image")] + [Authenticated] public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index b95e18a0d9..993b69601c 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -13,10 +13,16 @@ namespace MediaBrowser.Api { public class BaseRefreshRequest : IReturnVoid { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } + [ApiMember(Name = "MetadataRefreshMode", Description = "Specifies the metadata refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public MetadataRefreshMode MetadataRefreshMode { get; set; } - [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced during the refresh.", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public ImageRefreshMode ImageRefreshMode { get; set; } + + [ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public bool ReplaceAllMetadata { get; set; } + + [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool ReplaceAllImages { get; set; } } @@ -93,7 +99,7 @@ namespace MediaBrowser.Api private async Task RefreshItem(RefreshItem request, BaseItem item) { var options = GetRefreshOptions(request); - + try { await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); @@ -148,10 +154,10 @@ namespace MediaBrowser.Api { return new MetadataRefreshOptions { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = ImageRefreshMode.FullRefresh, - ReplaceAllMetadata = request.Forced, - ReplaceAllImages = request.ReplaceAllImages + MetadataRefreshMode = request.MetadataRefreshMode, + ImageRefreshMode = request.ImageRefreshMode, + ReplaceAllImages = request.ReplaceAllImages, + ReplaceAllMetadata = request.ReplaceAllMetadata }; } } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 77a714755d..10aa231265 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -226,6 +227,7 @@ namespace MediaBrowser.Api.Library /// /// Class LibraryService /// + [Authenticated] public class LibraryService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs deleted file mode 100644 index 4fc3e00c08..0000000000 --- a/MediaBrowser.Api/Library/SubtitleService.cs +++ /dev/null @@ -1,184 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.Library -{ - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")] - public class GetSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Format { get; set; } - - [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long StartPositionTicks { get; set; } - } - - [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] - public class DeleteSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] - public int Index { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] - public class SearchRemoteSubtitles : IReturn> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Language { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")] - public class GetSubtitleProviders : IReturn> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] - public class DownloadRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SubtitleId { get; set; } - } - - [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] - public class GetRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Authenticated] - public class SubtitleService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly ISubtitleManager _subtitleManager; - private readonly ISubtitleEncoder _subtitleEncoder; - - public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder) - { - _libraryManager = libraryManager; - _subtitleManager = subtitleManager; - _subtitleEncoder = subtitleEncoder; - } - - public object Get(SearchRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result; - - return ToOptimizedResult(response); - } - public object Get(GetSubtitle request) - { - if (string.IsNullOrEmpty(request.Format)) - { - var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - - var mediaSource = item.GetMediaSources(false) - .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); - - return ToStaticFileResult(subtitleStream.Path); - } - - var stream = GetSubtitles(request).Result; - - return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format)); - } - - private async Task GetSubtitles(GetSubtitle request) - { - return await _subtitleEncoder.GetSubtitles(request.Id, - request.MediaSourceId, - request.Index, - request.Format, - request.StartPositionTicks, - CancellationToken.None).ConfigureAwait(false); - } - - public void Delete(DeleteSubtitle request) - { - var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index); - - Task.WaitAll(task); - } - - public object Get(GetSubtitleProviders request) - { - var result = _subtitleManager.GetProviders(request.Id); - - return ToOptimizedResult(result); - } - - public object Get(GetRemoteSubtitles request) - { - var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result; - - return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format)); - } - - public void Post(DownloadRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - Task.Run(async () => - { - try - { - await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) - .ConfigureAwait(false); - - await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error downloading subtitles", ex); - } - - }); - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a68966b335..ca2887d19c 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -65,11 +65,12 @@ Properties\SharedVersion.cs + - + @@ -139,7 +140,6 @@ - diff --git a/MediaBrowser.Api/NotificationsService.cs b/MediaBrowser.Api/NotificationsService.cs index 28edb61dd9..51a0801060 100644 --- a/MediaBrowser.Api/NotificationsService.cs +++ b/MediaBrowser.Api/NotificationsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; using MediaBrowser.Model.Notifications; using ServiceStack; @@ -82,6 +83,7 @@ namespace MediaBrowser.Api public string Ids { get; set; } } + [Authenticated] public class NotificationsService : BaseApiService { private readonly INotificationsRepository _notificationsRepo; diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 00c307a18b..f4651601b0 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using ServiceStack; @@ -14,6 +15,7 @@ namespace MediaBrowser.Api /// Class GetSessions /// [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] + [Authenticated] public class GetSessions : IReturn> { [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -27,6 +29,7 @@ namespace MediaBrowser.Api /// Class DisplayContent /// [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] + [Authenticated] public class DisplayContent : IReturnVoid { /// @@ -59,6 +62,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] + [Authenticated] public class Play : IReturnVoid { /// @@ -91,6 +95,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] + [Authenticated] public class SendPlaystateCommand : IReturnVoid { /// @@ -115,6 +120,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendSystemCommand : IReturnVoid { /// @@ -133,6 +139,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendGeneralCommand : IReturnVoid { /// @@ -151,6 +158,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendFullGeneralCommand : GeneralCommand, IReturnVoid { /// @@ -162,6 +170,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] + [Authenticated] public class SendMessageCommand : IReturnVoid { /// @@ -182,6 +191,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] + [Authenticated] public class AddUserToSession : IReturnVoid { [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] @@ -192,6 +202,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] + [Authenticated] public class RemoveUserFromSession : IReturnVoid { [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] @@ -202,7 +213,6 @@ namespace MediaBrowser.Api } [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")] public class PostCapabilities : IReturnVoid { /// diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs new file mode 100644 index 0000000000..3e692cb22f --- /dev/null +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -0,0 +1,189 @@ +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Subtitles +{ + [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] + [Authenticated] + public class DeleteSubtitle + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + + [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] + public int Index { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] + [Authenticated] + public class SearchRemoteSubtitles : IReturn> + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Language { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")] + [Authenticated] + public class GetSubtitleProviders : IReturn> + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] + [Authenticated] + public class DownloadRemoteSubtitles : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SubtitleId { get; set; } + } + + [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] + [Authenticated] + public class GetRemoteSubtitles : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")] + public class GetSubtitle + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + + [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + public int Index { get; set; } + + [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Format { get; set; } + + [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public long StartPositionTicks { get; set; } + } + + public class SubtitleService : BaseApiService + { + private readonly ILibraryManager _libraryManager; + private readonly ISubtitleManager _subtitleManager; + private readonly ISubtitleEncoder _subtitleEncoder; + + public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder) + { + _libraryManager = libraryManager; + _subtitleManager = subtitleManager; + _subtitleEncoder = subtitleEncoder; + } + + public object Get(GetSubtitle request) + { + if (string.IsNullOrEmpty(request.Format)) + { + var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); + + var mediaSource = item.GetMediaSources(false) + .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); + + var subtitleStream = mediaSource.MediaStreams + .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); + + return ToStaticFileResult(subtitleStream.Path); + } + + var stream = GetSubtitles(request).Result; + + return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format)); + } + + private async Task GetSubtitles(GetSubtitle request) + { + return await _subtitleEncoder.GetSubtitles(request.Id, + request.MediaSourceId, + request.Index, + request.Format, + request.StartPositionTicks, + CancellationToken.None).ConfigureAwait(false); + } + + public object Get(SearchRemoteSubtitles request) + { + var video = (Video)_libraryManager.GetItemById(request.Id); + + var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result; + + return ToOptimizedResult(response); + } + + public void Delete(DeleteSubtitle request) + { + var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index); + + Task.WaitAll(task); + } + + public object Get(GetSubtitleProviders request) + { + var result = _subtitleManager.GetProviders(request.Id); + + return ToOptimizedResult(result); + } + + public object Get(GetRemoteSubtitles request) + { + var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result; + + return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format)); + } + + public void Post(DownloadRemoteSubtitles request) + { + var video = (Video)_libraryManager.GetItemById(request.Id); + + Task.Run(async () => + { + try + { + await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) + .ConfigureAwait(false); + + await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error downloading subtitles", ex); + } + + }); + } + } +} diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 2f0741434a..6f2e83a796 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -1,6 +1,12 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.System; using ServiceStack; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Api @@ -18,15 +24,30 @@ namespace MediaBrowser.Api /// Class RestartApplication /// [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] + [Authenticated] public class RestartApplication { } [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] + [Authenticated] public class ShutdownApplication { } + [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] + [Authenticated] + public class GetServerLogs : IReturn> + { + } + + [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] + public class GetLogFile + { + [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Name { get; set; } + } + /// /// Class SystemInfoService /// @@ -36,16 +57,59 @@ namespace MediaBrowser.Api /// The _app host /// private readonly IServerApplicationHost _appHost; - + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// /// The app host. /// jsonSerializer - public SystemService(IServerApplicationHost appHost) + public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) { _appHost = appHost; + _appPaths = appPaths; + _fileSystem = fileSystem; + } + + public object Get(GetServerLogs request) + { + List files; + + try + { + files = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List(); + } + + var result = files.Select(i => new LogFile + { + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i), + Name = i.Name, + Size = i.Length + + }).OrderByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ThenBy(i => i.Name) + .ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetLogFile request) + { + var file = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase)); + + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); } /// diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index d1767e7fd9..55cdc8681e 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -717,9 +717,7 @@ namespace MediaBrowser.Api.UserLibrary await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false); - data = _userDataRepository.GetUserData(user.Id, key); - - return _dtoService.GetUserItemDataDto(data); + return _userDataRepository.GetUserDataDto(item, user); } /// @@ -766,9 +764,7 @@ namespace MediaBrowser.Api.UserLibrary await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false); - data = _userDataRepository.GetUserData(user.Id, key); - - return _dtoService.GetUserItemDataDto(data); + return _userDataRepository.GetUserDataDto(item, user); } /// @@ -936,7 +932,7 @@ namespace MediaBrowser.Api.UserLibrary await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false); } - return _dtoService.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey())); + return _userDataRepository.GetUserDataDto(item, user); } } } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 64d1fcb34c..cda489c94a 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.Api /// Class DeleteUser /// [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")] + [Authenticated] public class DeleteUser : IReturnVoid { /// @@ -107,6 +108,7 @@ namespace MediaBrowser.Api /// Class UpdateUserPassword /// [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")] + [Authenticated] public class UpdateUserPassword : IReturnVoid { /// @@ -138,6 +140,7 @@ namespace MediaBrowser.Api /// Class UpdateUser /// [Route("/Users/{Id}", "POST", Summary = "Updates a user")] + [Authenticated] public class UpdateUser : UserDto, IReturnVoid { } @@ -146,6 +149,7 @@ namespace MediaBrowser.Api /// Class CreateUser /// [Route("/Users", "POST", Summary = "Creates a user")] + [Authenticated] public class CreateUser : UserDto, IReturn { } diff --git a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs deleted file mode 100644 index 46dabb0420..0000000000 --- a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs +++ /dev/null @@ -1,149 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class ScheduledTasksWebSocketListener - /// - public class LogFileWebSocketListener : BasePeriodicWebSocketListener, LogFileWebSocketState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "LogFile"; } - } - - /// - /// The _kernel - /// - private readonly ILogManager _logManager; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The log manager. - public LogFileWebSocketListener(ILogger logger, ILogManager logManager, IFileSystem fileSystem) - : base(logger) - { - _logManager = logManager; - _fileSystem = fileSystem; - _logManager.LoggerLoaded += kernel_LoggerLoaded; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// IEnumerable{System.String}. - protected override async Task> GetDataToSend(LogFileWebSocketState state) - { - if (!string.Equals(_logManager.LogFilePath, state.LastLogFilePath)) - { - state.LastLogFilePath = _logManager.LogFilePath; - state.StartLine = 0; - } - - var lines = await GetLogLines(state.LastLogFilePath, state.StartLine, _fileSystem).ConfigureAwait(false); - - state.StartLine += lines.Count; - - return lines; - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) - { - if (dispose) - { - _logManager.LoggerLoaded -= kernel_LoggerLoaded; - } - base.Dispose(dispose); - } - - /// - /// Handles the LoggerLoaded event of the kernel control. - /// - /// The source of the event. - /// The instance containing the event data. - void kernel_LoggerLoaded(object sender, EventArgs e) - { - // Reset the startline for each connection whenever the logger reloads - lock (ActiveConnections) - { - foreach (var connection in ActiveConnections) - { - connection.Item4.StartLine = 0; - } - } - } - - /// - /// Gets the log lines. - /// - /// The log file path. - /// The start line. - /// Task{IEnumerable{System.String}}. - internal static async Task> GetLogLines(string logFilePath, int startLine, IFileSystem fileSystem) - { - var lines = new List(); - - using (var fs = fileSystem.GetFileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) - { - using (var reader = new StreamReader(fs)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - if (line.IndexOf("Info -", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Warn -", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Error -", StringComparison.OrdinalIgnoreCase) != -1) - { - lines.Add(line); - } - } - } - } - - if (startLine > 0) - { - lines = lines.Skip(startLine).ToList(); - } - - return lines; - } - } - - /// - /// Class LogFileWebSocketState - /// - public class LogFileWebSocketState : WebSocketListenerState - { - /// - /// Gets or sets the last log file path. - /// - /// The last log file path. - public string LastLogFilePath { get; set; } - /// - /// Gets or sets the start line. - /// - /// The start line. - public int StartLine { get; set; } - } -} diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs index 0cc4fc6b4d..dcac5e7ba6 100644 --- a/MediaBrowser.Common/Net/MimeTypes.cs +++ b/MediaBrowser.Common/Net/MimeTypes.cs @@ -199,6 +199,10 @@ namespace MediaBrowser.Common.Net { return "application/x-javascript"; } + if (ext.Equals(".map", StringComparison.OrdinalIgnoreCase)) + { + return "application/x-javascript"; + } if (ext.Equals(".woff", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index f9d7cc21af..0d0555dc0d 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -25,13 +25,6 @@ namespace MediaBrowser.Controller.Dto /// System.String. string GetDtoId(BaseItem item); - /// - /// Gets the user item data dto. - /// - /// The data. - /// UserItemDataDto. - UserItemDataDto GetUserItemDataDto(UserItemData data); - /// /// Attaches the primary image aspect ratio. /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 524d7097ba..0428347310 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; @@ -1571,5 +1572,19 @@ namespace MediaBrowser.Controller.Entities return path; } + + public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + if (RunTimeTicks.HasValue) + { + double pct = RunTimeTicks.Value; + + if (pct > 0) + { + pct = userData.PlaybackPositionTicks / pct; + dto.PlayedPercentage = 100 * pct; + } + } + } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 584091b130..b886cef193 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MoreLinq; using System; @@ -769,6 +770,11 @@ namespace MediaBrowser.Controller.Entities /// IEnumerable{BaseItem}. /// public virtual IEnumerable GetChildren(User user, bool includeLinkedChildren) + { + return GetChildren(user, includeLinkedChildren, false); + } + + internal IEnumerable GetChildren(User user, bool includeLinkedChildren, bool includeHidden) { if (user == null) { @@ -780,7 +786,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, includeHidden, false); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -796,9 +802,10 @@ namespace MediaBrowser.Controller.Entities /// The user. /// if set to true [include linked children]. /// The list. + /// if set to true [include hidden]. /// if set to true [recursive]. /// true if XXXX, false otherwise - private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive) + private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool includeHidden, bool recursive) { var hasLinkedChildren = false; @@ -806,7 +813,7 @@ namespace MediaBrowser.Controller.Entities { if (child.IsVisible(user)) { - if (!child.IsHiddenFromUser(user)) + if (includeHidden || !child.IsHiddenFromUser(user)) { list.Add(child); } @@ -815,7 +822,7 @@ namespace MediaBrowser.Controller.Entities { var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true)) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, includeHidden, true)) { hasLinkedChildren = true; } @@ -855,7 +862,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, true); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -1069,5 +1076,68 @@ namespace MediaBrowser.Controller.Entities return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual) .All(i => i.IsUnplayed(user)); } + + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + var recursiveItemCount = 0; + var unplayed = 0; + + double totalPercentPlayed = 0; + + IEnumerable children; + var folder = this; + + var season = folder as Season; + + if (season != null) + { + children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual); + } + else + { + children = folder.GetRecursiveChildren(user) + .Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual); + } + + // Loop through each recursive child + foreach (var child in children) + { + recursiveItemCount++; + + var isUnplayed = true; + + var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey()); + + // Incrememt totalPercentPlayed + if (itemUserData != null) + { + if (itemUserData.Played) + { + totalPercentPlayed += 100; + + isUnplayed = false; + } + else if (itemUserData.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) + { + double itemPercent = itemUserData.PlaybackPositionTicks; + itemPercent /= child.RunTimeTicks.Value; + totalPercentPlayed += itemPercent; + } + } + + if (isUnplayed) + { + unplayed++; + } + } + + dto.UnplayedItemCount = unplayed; + + if (recursiveItemCount > 0) + { + dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount; + dto.Played = dto.PlayedPercentage.Value >= 100; + } + } } } diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs index 780181a61d..d576d90c45 100644 --- a/MediaBrowser.Controller/Entities/IHasUserData.cs +++ b/MediaBrowser.Controller/Entities/IHasUserData.cs @@ -1,4 +1,6 @@ - +using MediaBrowser.Model.Dto; +using System; + namespace MediaBrowser.Controller.Entities { /// @@ -6,10 +8,24 @@ namespace MediaBrowser.Controller.Entities /// public interface IHasUserData { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + Guid Id { get; set; } + /// /// Gets the user data key. /// /// System.String. string GetUserDataKey(); + + /// + /// Fills the user data dto values. + /// + /// The dto. + /// The user data. + /// The user. + void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user); } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index b9630a66f4..847183fd00 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return FindParent(); + return Season; } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index e5a8135c2d..6404e71ec6 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using System; using System.Collections.Generic; using System.Linq; @@ -51,5 +52,10 @@ namespace MediaBrowser.Controller.Entities LibraryManager.RegisterItem(item); } } + + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + // Nothing meaninful here and will only waste resources + } } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 86099fdc01..619a497f5f 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList(); return user.RootFolder - .GetChildren(user, true) + .GetChildren(user, true, true) .OfType() .Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i)); } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 2bec9e3deb..9db91e7f22 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System; using System.Threading; @@ -34,5 +35,13 @@ namespace MediaBrowser.Controller.Library /// The key. /// Task{UserItemData}. UserItemData GetUserData(Guid userId, string key); + + /// + /// Gets the user data dto. + /// + /// The item. + /// The user. + /// UserItemDataDto. + UserItemDataDto GetUserDataDto(IHasUserData item, User user); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 23610351e7..ba1cb30436 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { - public interface ILiveTvRecording : IHasImages, IHasMediaSources + public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData { string ServiceName { get; set; } @@ -20,8 +20,6 @@ namespace MediaBrowser.Controller.LiveTv string GetClientTypeName(); - string GetUserDataKey(); - bool IsParentalAllowed(User user); Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 692d6db909..7f1ddbce9b 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -1,6 +1,6 @@ -using System; +using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Providers { @@ -18,6 +18,11 @@ namespace MediaBrowser.Controller.Providers /// [Obsolete] public bool ForceSave { get; set; } + + public MetadataRefreshOptions() + { + MetadataRefreshMode = MetadataRefreshMode.Default; + } } public class ImageRefreshOptions @@ -38,48 +43,54 @@ namespace MediaBrowser.Controller.Providers public bool IsReplacingImage(ImageType type) { - return ReplaceAllImages || ReplaceImages.Contains(type); + return ImageRefreshMode == ImageRefreshMode.FullRefresh && + (ReplaceAllImages || ReplaceImages.Contains(type)); } } public enum MetadataRefreshMode { /// - /// Providers will be executed based on default rules + /// The none /// - EnsureMetadata = 0, + None = 0, /// - /// No providers will be executed + /// The validation only /// - None = 1, + ValidationOnly = 1, /// - /// All providers will be executed to search for new metadata + /// Providers will be executed based on default rules /// - FullRefresh = 2, + Default = 2, /// - /// The validation only + /// All providers will be executed to search for new metadata /// - ValidationOnly = 3 + FullRefresh = 3 } public enum ImageRefreshMode { + /// + /// The none + /// + None = 0, + /// /// The default /// - Default = 0, + Default = 1, /// /// Existing images will be validated /// - ValidationOnly = 1, + ValidationOnly = 2, /// /// All providers will be executed to search for new metadata /// - FullRefresh = 2 + FullRefresh = 3 } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6d3a9d20cf..7b20621827 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -219,7 +219,13 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. - Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint); + Task AuthenticateNewSession(string username, + string password, + string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint); /// /// Reports the capabilities. diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 0ed49d5f89..91c1508fc1 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -77,6 +77,9 @@ ApiClient\SessionUpdatesEventArgs.cs + + Branding\BrandingOptions.cs + Channels\ChannelFeatures.cs @@ -815,6 +818,9 @@ Session\UserDataChangeInfo.cs + + System\LogFile.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 12c87ca97a..782e8524db 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -64,6 +64,9 @@ ApiClient\SessionUpdatesEventArgs.cs + + Branding\BrandingOptions.cs + Channels\ChannelFeatures.cs @@ -796,6 +799,9 @@ Session\UserDataChangeInfo.cs + + System\LogFile.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs new file mode 100644 index 0000000000..737cb5c48f --- /dev/null +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Model.Branding +{ + public class BrandingOptions + { + /// + /// Gets or sets the login disclaimer. + /// + /// The login disclaimer. + public string LoginDisclaimer { get; set; } + } +} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 36c353479f..49b731341a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -158,9 +158,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableTmdbUpdates { get; set; } public bool EnableFanArtUpdates { get; set; } - public bool RequireMobileManualLogin { get; set; } - public bool RequireNonMobileManualLogin { get; set; } - /// /// Gets or sets the image saving convention. /// diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index db8b69951a..86eb40b975 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -10,12 +10,15 @@ namespace MediaBrowser.Model.Configuration public bool SaveImagePathsInNfo { get; set; } public bool EnablePathSubstitution { get; set; } + public bool EnableExtraThumbsDuplication { get; set; } + public XbmcMetadataOptions() { ReleaseDateFormat = "yyyy-MM-dd"; SaveImagePathsInNfo = true; EnablePathSubstitution = true; + EnableExtraThumbsDuplication = true; } } } diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index 26b0e9d9e8..6ee9f19163 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -1,6 +1,6 @@ -using System; +using MediaBrowser.Model.Extensions; +using System; using System.ComponentModel; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dto { @@ -15,6 +15,18 @@ namespace MediaBrowser.Model.Dto /// The rating. public double? Rating { get; set; } + /// + /// Gets or sets the played percentage. + /// + /// The played percentage. + public double? PlayedPercentage { get; set; } + + /// + /// Gets or sets the unplayed item count. + /// + /// The unplayed item count. + public int? UnplayedItemCount { get; set; } + /// /// Gets or sets the playback position ticks. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index d758f2f394..4d4ca8e20c 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -65,6 +65,7 @@ + @@ -294,6 +295,7 @@ + diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs index f511d41a97..6e9368f441 100644 --- a/MediaBrowser.Model/Notifications/NotificationRequest.cs +++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Configuration; -using System; +using System; using System.Collections.Generic; namespace MediaBrowser.Model.Notifications diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs index 40723eff83..98df3efe55 100644 --- a/MediaBrowser.Model/Session/SessionInfoDto.cs +++ b/MediaBrowser.Model/Session/SessionInfoDto.cs @@ -1,32 +1,20 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Session { [DebuggerDisplay("Client = {Client}, Username = {UserName}")] public class SessionInfoDto : IHasPropertyChangedEvent { - /// - /// Gets or sets a value indicating whether this instance can seek. - /// - /// true if this instance can seek; otherwise, false. - public bool CanSeek { get; set; } - /// /// Gets or sets the supported commands. /// /// The supported commands. public List SupportedCommands { get; set; } - - /// - /// Gets or sets the remote end point. - /// - /// The remote end point. - public string RemoteEndPoint { get; set; } /// /// Gets or sets the queueable media types. @@ -99,18 +87,6 @@ namespace MediaBrowser.Model.Session /// /// The name of the device. public string DeviceName { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is paused. - /// - /// true if this instance is paused; otherwise, false. - public bool IsPaused { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is muted. - /// - /// true if this instance is muted; otherwise, false. - public bool IsMuted { get; set; } /// /// Gets or sets the now playing item. @@ -118,12 +94,6 @@ namespace MediaBrowser.Model.Session /// The now playing item. public BaseItemInfo NowPlayingItem { get; set; } - /// - /// Gets or sets the now playing position ticks. - /// - /// The now playing position ticks. - public long? NowPlayingPositionTicks { get; set; } - /// /// Gets or sets the device id. /// diff --git a/MediaBrowser.Model/System/LogFile.cs b/MediaBrowser.Model/System/LogFile.cs new file mode 100644 index 0000000000..ba409c5423 --- /dev/null +++ b/MediaBrowser.Model/System/LogFile.cs @@ -0,0 +1,31 @@ +using System; + +namespace MediaBrowser.Model.System +{ + public class LogFile + { + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date modified. + /// + /// The date modified. + public DateTime DateModified { get; set; } + + /// + /// Gets or sets the size. + /// + /// The size. + public long Size { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } +} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 12af93dbd1..1b2e9fa6d6 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -441,11 +442,16 @@ namespace MediaBrowser.Providers.Manager var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); - return new[] - { - Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension), - Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension) - }; + var list = new List + { + Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension) + }; + + if (EnableExtraThumbsDuplication) + { + list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); + } + return list.ToArray(); } if (type == ImageType.Primary) @@ -528,6 +534,16 @@ namespace MediaBrowser.Providers.Manager return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) }; } + private bool EnableExtraThumbsDuplication + { + get + { + var config = _config.GetConfiguration("xbmcmetadata"); + + return config.EnableExtraThumbsDuplication; + } + } + /// /// Gets the save path for item in mixed folder. /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 92b4616e71..57a40741f5 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -234,7 +234,7 @@ namespace MediaBrowser.Providers.MediaInfo await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || - options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata) + options.MetadataRefreshMode == MetadataRefreshMode.Default) { try { @@ -460,7 +460,7 @@ namespace MediaBrowser.Providers.MediaInfo var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, false).ToList(); - var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata || + var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && diff --git a/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs new file mode 100644 index 0000000000..d6cd3424b1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Branding; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Branding +{ + public class BrandingConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + ConfigurationType = typeof(BrandingOptions), + Key = "branding" + } + }; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 73216ca33c..62ff9f6879 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -212,6 +212,12 @@ namespace MediaBrowser.Server.Implementations.Dto { if (item.IsFolder) { + var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); + + // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice + // TODO: Improve in future + dto.UserData = GetUserItemDataDto(userData); + var folder = (Folder)item; dto.ChildCount = GetChildCount(folder, user); @@ -220,15 +226,15 @@ namespace MediaBrowser.Server.Implementations.Dto { SetSpecialCounts(folder, user, dto, fields); } - } - var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); - - dto.UserData = GetUserItemDataDto(userData); + dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100; + dto.UserData.PlayedPercentage = dto.PlayedPercentage; + dto.UserData.UnplayedItemCount = dto.RecursiveUnplayedItemCount; + } - if (item.IsFolder) + else { - dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100; + dto.UserData = _userDataRepository.GetUserDataDto(item, user); } dto.PlayAccess = item.GetPlayAccess(user); @@ -1110,16 +1116,17 @@ namespace MediaBrowser.Server.Implementations.Dto if (episode != null) { - series = item.FindParent(); + series = episode.Series; - dto.SeriesId = GetDtoId(series); - dto.SeriesName = series.Name; - dto.AirTime = series.AirTime; - dto.SeriesStudio = series.Studios.FirstOrDefault(); - - dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb); - - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + if (series != null) + { + dto.SeriesId = GetDtoId(series); + dto.SeriesName = series.Name; + dto.AirTime = series.AirTime; + dto.SeriesStudio = series.Studios.FirstOrDefault(); + dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb); + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + } } // Add SeasonInfo @@ -1127,14 +1134,17 @@ namespace MediaBrowser.Server.Implementations.Dto if (season != null) { - series = item.FindParent(); + series = season.Series; - dto.SeriesId = GetDtoId(series); - dto.SeriesName = series.Name; - dto.AirTime = series.AirTime; - dto.SeriesStudio = series.Studios.FirstOrDefault(); + if (series != null) + { + dto.SeriesId = GetDtoId(series); + dto.SeriesName = series.Name; + dto.AirTime = series.AirTime; + dto.SeriesStudio = series.Studios.FirstOrDefault(); - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + } } var game = item as Game; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index d7186aa211..c31f462150 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,10 +1,11 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; +using MoreLinq; using System; using System.Collections.Generic; using System.Linq; @@ -17,21 +18,21 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly IDtoService _dtoService; private readonly IUserDataManager _userDataManager; + private readonly IUserManager _userManager; private readonly object _syncLock = new object(); private Timer UpdateTimer { get; set; } private const int UpdateDuration = 500; - private readonly Dictionary> _changedKeys = new Dictionary>(); + private readonly Dictionary> _changedItems = new Dictionary>(); - public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IDtoService dtoService, ILogger logger) + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) { _userDataManager = userDataManager; _sessionManager = sessionManager; - _dtoService = dtoService; _logger = logger; + _userManager = userManager; } public void Run() @@ -58,15 +59,28 @@ namespace MediaBrowser.Server.Implementations.EntryPoints UpdateTimer.Change(UpdateDuration, Timeout.Infinite); } - List keys; + List keys; - if (!_changedKeys.TryGetValue(e.UserId, out keys)) + if (!_changedItems.TryGetValue(e.UserId, out keys)) { - keys = new List(); - _changedKeys[e.UserId] = keys; + keys = new List(); + _changedItems[e.UserId] = keys; } - keys.Add(e.Key); + keys.Add(e.Item); + + var baseItem = e.Item as BaseItem; + + // Go up one level for indicators + if (baseItem != null) + { + var parent = baseItem.Parent; + + if (parent != null) + { + keys.Add(parent); + } + } } } @@ -75,8 +89,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints lock (_syncLock) { // Remove dupes in case some were saved multiple times - var changes = _changedKeys.ToList(); - _changedKeys.Clear(); + var changes = _changedItems.ToList(); + _changedItems.Clear(); SendNotifications(changes, CancellationToken.None); @@ -88,7 +102,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints } } - private async Task SendNotifications(IEnumerable>> changes, CancellationToken cancellationToken) + private async Task SendNotifications(IEnumerable>> changes, CancellationToken cancellationToken) { foreach (var pair in changes) { @@ -99,8 +113,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints if (userSessions.Count > 0) { + var user = _userManager.GetUserById(userId); + var dtoList = pair.Value - .Select(i => _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(userId, i))) + .DistinctBy(i => i.Id) + .Select(i => _userDataManager.GetUserDataDto(i, user)) .ToList(); var info = new UserDataChangeInfo diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 833dfc5e45..cfcbb077e9 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -363,19 +363,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer { try { - var errorResponse = new ErrorResponse - { - ResponseStatus = new ResponseStatus - { - ErrorCode = ex.GetType().GetOperationName(), - Message = ex.Message, - StackTrace = ex.StackTrace, - } - }; - var operationName = context.Request.GetOperationName(); var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; + + if (httpRes.IsClosed) + { + return; + } + var contentType = httpReq.ResponseContentType; var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType); @@ -398,6 +394,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.ContentType = contentType; + var errorResponse = new ErrorResponse + { + ResponseStatus = new ResponseStatus + { + ErrorCode = ex.GetType().GetOperationName(), + Message = ex.Message, + StackTrace = ex.StackTrace, + } + }; + serializer(httpReq, errorResponse, httpRes); httpRes.Close(); diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 77343ab4ef..94be37e956 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -36,6 +36,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security auth.TryGetValue("Version", out version); } + var token = httpReq.Headers["X-MediaBrowser-Token"]; + + if (string.IsNullOrWhiteSpace(token)) + { + token = httpReq.QueryString["api_key"]; + } + return new AuthorizationInfo { Client = client, @@ -43,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security DeviceId = deviceId, UserId = userId, Version = version, - Token = httpReq.Headers["X-AUTH-TOKEN"] + Token = token }; } diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 79f1265116..d3030f31f9 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -125,5 +126,41 @@ namespace MediaBrowser.Server.Implementations.Library { return userId + key; } + + public UserItemDataDto GetUserDataDto(IHasUserData item, User user) + { + var userData = GetUserData(user.Id, item.GetUserDataKey()); + var dto = GetUserItemDataDto(userData); + + item.FillUserDataDtoValues(dto, userData, user); + + return dto; + } + + /// + /// Converts a UserItemData to a DTOUserItemData + /// + /// The data. + /// DtoUserItemData. + /// + private UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + Key = data.Key + }; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 412b2e7bd0..9c69e656d7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; @@ -23,15 +22,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; - private readonly IItemRepository _itemRepo; - public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IItemRepository itemRepo) + public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger) { _dtoService = dtoService; _userDataManager = userDataManager; _imageProcessor = imageProcessor; _logger = logger; - _itemRepo = itemRepo; } public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel) @@ -249,7 +246,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(recording, user); dto.PlayAccess = recording.GetPlayAccess(user); } @@ -322,7 +319,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(info, user); dto.PlayAccess = info.GetPlayAccess(user); } @@ -401,7 +398,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, item.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(item, user); dto.PlayAccess = item.GetPlayAccess(user); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index ad1ddba881..09793f4fc7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -40,7 +40,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - private readonly IJsonSerializer _json; private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; @@ -58,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1); - public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, IJsonSerializer json, ILocalizationManager localization) + public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization) { _config = config; _fileSystem = fileSystem; @@ -67,12 +66,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv _userManager = userManager; _libraryManager = libraryManager; _taskManager = taskManager; - _json = json; _localization = localization; _dtoService = dtoService; _userDataManager = userDataManager; - _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo); + _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger); } /// diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 3bd0df0eb5..d6d4bbc516 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -258,11 +258,11 @@ "LabelCachePath": "Cache path:", "LabelCachePathHelp": "This folder contains server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", - "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder.", + "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", "TabTV": "TV", "TabGames": "Games", @@ -284,7 +284,7 @@ "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", "LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", - "OptionImageSavingCompatible": "Compatible - Media Browser/Plex/Xbmc", + "OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex", "OptionImageSavingStandard": "Standard - MB2", "ButtonSignIn": "Sign In", "TitleSignIn": "Sign In", @@ -849,5 +849,14 @@ "LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.", "LabelGroupChannelsIntoViews": "Display the following channels directly within my views:", "LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.", - "LabelDisplayCollectionsView": "Display a Collections view to show movie collections" + "LabelDisplayCollectionsView": "Display a Collections view to show movie collections", + "LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs", + "LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.", + "TabServices": "Services", + "TabLogs": "Logs", + "HeaderServerLogFiles": "Server log files:", + "TabBranding": "Branding", + "HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.", + "LabelLoginDisclaimer": "Login disclaimer:", + "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a909929ae0..1d201e0690 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -101,6 +101,7 @@ Properties\SharedVersion.cs + diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 964e2cd243..b832f3a06b 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Notifications; using System; @@ -93,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.Notifications if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType)) { - return _userManager.Users.Where(i => _config.Configuration.NotificationOptions.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) + var config = GetConfiguration(); + + return _userManager.Users.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) .Select(i => i.Id.ToString("N")); } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 2f6790a3e8..2d85a3aa7f 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Common.Events; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -1185,6 +1187,24 @@ namespace MediaBrowser.Server.Implementations.Session }; } + private bool IsLocal(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + // Private address space: + // http://en.wikipedia.org/wiki/Private_network + + return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + } + /// /// Reports the capabilities. /// @@ -1283,15 +1303,10 @@ namespace MediaBrowser.Server.Implementations.Session DeviceName = session.DeviceName, Id = session.Id, LastActivityDate = session.LastActivityDate, - NowPlayingPositionTicks = session.PlayState.PositionTicks, - IsPaused = session.PlayState.IsPaused, - IsMuted = session.PlayState.IsMuted, NowViewingItem = session.NowViewingItem, ApplicationVersion = session.ApplicationVersion, - CanSeek = session.PlayState.CanSeek, QueueableMediaTypes = session.QueueableMediaTypes, PlayableMediaTypes = session.PlayableMediaTypes, - RemoteEndPoint = session.RemoteEndPoint, AdditionalUsers = session.AdditionalUsers, SupportedCommands = session.SupportedCommands, UserName = session.UserName, diff --git a/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs index 46c3df07b7..7e6a252cdc 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs @@ -26,13 +26,36 @@ namespace MediaBrowser.Server.Implementations.Sorting /// System.String. private DateTime GetValue(BaseItem x) { - var series = (x as Series) ?? x.FindParent(); + var series = x as Series; - DateTime result; - if (series != null && DateTime.TryParse(series.AirTime, out result)) + if (series == null) { - return result; - } + var season = x as Season; + + if (season != null) + { + series = season.Series; + } + else + { + var episode = x as Episode; + + if (episode != null) + { + series = episode.Series; + } + } + } + + if (series != null) + { + DateTime result; + if (DateTime.TryParse(series.AirTime, out result)) + { + return result; + } + } + return DateTime.MinValue; } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 673a9f1519..91e92e21c5 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -654,7 +654,7 @@ namespace MediaBrowser.ServerApplication var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); RegisterSingleInstance(collectionManager); - LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, JsonSerializer, LocalizationManager); + LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager); diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs index 4ba98e9b69..c2c64ea4d4 100644 --- a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -86,18 +86,6 @@ namespace MediaBrowser.ServerApplication.Native appHost.WebApplicationName + "/swagger-ui/index.html", logger); } - /// - /// Opens the standard API documentation. - /// - /// The configuration manager. - /// The app host. - /// The logger. - public static void OpenStandardApiDocumentation(IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger) - { - OpenUrl("http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + - appHost.WebApplicationName + "/metadata", logger); - } - /// /// Opens the URL. /// diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index f5f9434e7d..47a4be8e36 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -29,7 +29,6 @@ namespace MediaBrowser.ServerApplication private System.Windows.Forms.ToolStripMenuItem cmdLogWindow; private System.Windows.Forms.ToolStripMenuItem cmdCommunity; private System.Windows.Forms.ToolStripMenuItem cmdApiDocs; - private System.Windows.Forms.ToolStripMenuItem cmdStandardDocs; private System.Windows.Forms.ToolStripMenuItem cmdSwagger; private System.Windows.Forms.ToolStripMenuItem cmdGtihub; @@ -90,7 +89,6 @@ namespace MediaBrowser.ServerApplication cmdConfigure = new System.Windows.Forms.ToolStripMenuItem(); cmdBrowse = new System.Windows.Forms.ToolStripMenuItem(); cmdApiDocs = new System.Windows.Forms.ToolStripMenuItem(); - cmdStandardDocs = new System.Windows.Forms.ToolStripMenuItem(); cmdSwagger = new System.Windows.Forms.ToolStripMenuItem(); cmdGtihub = new System.Windows.Forms.ToolStripMenuItem(); @@ -169,17 +167,11 @@ namespace MediaBrowser.ServerApplication // cmdApiDocs // cmdApiDocs.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - cmdStandardDocs, cmdSwagger, cmdGtihub}); cmdApiDocs.Name = "cmdApiDocs"; cmdApiDocs.Size = new System.Drawing.Size(208, 22); // - // cmdStandardDocs - // - cmdStandardDocs.Name = "cmdStandardDocs"; - cmdStandardDocs.Size = new System.Drawing.Size(136, 22); - // // cmdSwagger // cmdSwagger.Name = "cmdSwagger"; @@ -199,7 +191,6 @@ namespace MediaBrowser.ServerApplication cmdLibraryExplorer.Click += cmdLibraryExplorer_Click; cmdSwagger.Click += cmdSwagger_Click; - cmdStandardDocs.Click += cmdStandardDocs_Click; cmdGtihub.Click += cmdGtihub_Click; LoadLogWindow(null, EventArgs.Empty); @@ -224,7 +215,6 @@ namespace MediaBrowser.ServerApplication cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity"); cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki"); cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger"); - cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard"); cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation"); cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary"); cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser"); @@ -346,11 +336,6 @@ namespace MediaBrowser.ServerApplication BrowserLauncher.OpenGithub(_logger); } - void cmdStandardDocs_Click(object sender, EventArgs e) - { - BrowserLauncher.OpenStandardApiDocumentation(_configurationManager, _appHost, _logger); - } - void cmdSwagger_Click(object sender, EventArgs e) { BrowserLauncher.OpenSwagger(_configurationManager, _appHost, _logger); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 3ccf26c375..36adae71c7 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -521,6 +521,7 @@ namespace MediaBrowser.WebDashboard.Api "mediacontroller.js", "chromecast.js", "backdrops.js", + "branding.js", "mediaplayer.js", "mediaplayer-video.js", @@ -529,7 +530,6 @@ namespace MediaBrowser.WebDashboard.Api "ratingdialog.js", "aboutpage.js", - "allusersettings.js", "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", @@ -537,7 +537,6 @@ namespace MediaBrowser.WebDashboard.Api "advancedserversettings.js", "metadataadvanced.js", "appsplayback.js", - "appsweather.js", "autoorganizetv.js", "autoorganizelog.js", "channels.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 8f85fcb513..0099daaf19 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -98,6 +98,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -392,9 +395,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -617,6 +617,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1499,9 +1502,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -1748,11 +1748,6 @@ PreserveNewest - - - PreserveNewest - - PreserveNewest @@ -1814,11 +1809,6 @@ PreserveNewest - - - PreserveNewest - - PreserveNewest -- cgit v1.2.3 From d6aee6a2ed9f0882c73765bb27962a30d9e4a5ce Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Jul 2014 21:22:26 -0400 Subject: fixes #865 - Series Season folders --- MediaBrowser.Controller/Library/TVUtils.cs | 6 ++++++ MediaBrowser.Tests/Resolvers/TvUtilTests.cs | 2 ++ 2 files changed, 8 insertions(+) (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 0681a8cd1a..88eadda005 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -129,6 +129,12 @@ namespace MediaBrowser.Controller.Library return 0; } + int val; + if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + return val; + } + // Look for one of the season folder names foreach (var name in SeasonFolderNames) { diff --git a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs index bffd22f3ed..c10dcd3c5c 100644 --- a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs +++ b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs @@ -161,6 +161,8 @@ namespace MediaBrowser.Tests.Resolvers Assert.AreEqual(1, TVUtils.GetSeasonNumberFromPath(@"\Drive\Season 1")); Assert.AreEqual(1, TVUtils.GetSeasonNumberFromPath(@"\Drive\Season 1")); + Assert.AreEqual(2, TVUtils.GetSeasonNumberFromPath(@"\Drive\Seinfeld\2")); + //Four Digits seasons Assert.AreEqual(2009, TVUtils.GetSeasonNumberFromPath(@"\Drive\Season 2009")); Assert.AreEqual(2009, TVUtils.GetSeasonNumberFromPath(@"\Drive\Season 2009")); -- cgit v1.2.3 From ce20066bc0e2c7ba1634200cdee6ac339d4dfb60 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 20 Jul 2014 00:46:29 -0400 Subject: update translations --- MediaBrowser.Api/ItemRefreshService.cs | 3 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 5 + .../BaseApplicationHost.cs | 48 +- .../ScheduledTasks/Tasks/ReloadLoggerFileTask.cs | 2 +- MediaBrowser.Common/Net/IWebSocket.cs | 14 +- MediaBrowser.Common/Net/IWebSocketConnection.cs | 5 +- MediaBrowser.Controller/Library/TVUtils.cs | 8 +- .../Configuration/ServerConfiguration.cs | 7 - .../HttpServer/HttpListenerHost.cs | 2 +- .../HttpServer/NativeWebSocket.cs | 21 +- .../HttpServer/SocketSharp/SharpWebSocket.cs | 24 +- .../Library/Resolvers/TV/SeriesResolver.cs | 8 +- .../Localization/JavaScript/ar.json | 10 +- .../Localization/JavaScript/ca.json | 10 +- .../Localization/JavaScript/cs.json | 8 +- .../Localization/JavaScript/da.json | 10 +- .../Localization/JavaScript/de.json | 6 +- .../Localization/JavaScript/el.json | 10 +- .../Localization/JavaScript/en_GB.json | 10 +- .../Localization/JavaScript/en_US.json | 10 +- .../Localization/JavaScript/es.json | 6 +- .../Localization/JavaScript/es_MX.json | 138 +- .../Localization/JavaScript/fr.json | 6 +- .../Localization/JavaScript/he.json | 10 +- .../Localization/JavaScript/it.json | 42 +- .../Localization/JavaScript/javascript.json | 53 +- .../Localization/JavaScript/kk.json | 6 +- .../Localization/JavaScript/ms.json | 10 +- .../Localization/JavaScript/nb.json | 10 +- .../Localization/JavaScript/nl.json | 18 +- .../Localization/JavaScript/pl.json | 10 +- .../Localization/JavaScript/pt_BR.json | 6 +- .../Localization/JavaScript/pt_PT.json | 6 +- .../Localization/JavaScript/ru.json | 10 +- .../Localization/JavaScript/sv.json | 6 +- .../Localization/JavaScript/vi.json | 10 +- .../Localization/JavaScript/zh_TW.json | 10 +- .../Localization/Server/ar.json | 22 +- .../Localization/Server/ca.json | 22 +- .../Localization/Server/cs.json | 18 +- .../Localization/Server/da.json | 16 +- .../Localization/Server/de.json | 24 +- .../Localization/Server/el.json | 22 +- .../Localization/Server/en_GB.json | 22 +- .../Localization/Server/en_US.json | 22 +- .../Localization/Server/es.json | 20 +- .../Localization/Server/es_MX.json | 30 +- .../Localization/Server/fr.json | 26 +- .../Localization/Server/he.json | 18 +- .../Localization/Server/it.json | 58 +- .../Localization/Server/kk.json | 16 +- .../Localization/Server/ko.json | 22 +- .../Localization/Server/ms.json | 22 +- .../Localization/Server/nb.json | 22 +- .../Localization/Server/nl.json | 58 +- .../Localization/Server/pl.json | 22 +- .../Localization/Server/pt_BR.json | 20 +- .../Localization/Server/pt_PT.json | 22 +- .../Localization/Server/ru.json | 34 +- .../Localization/Server/server.json | 1793 ++++++++++---------- .../Localization/Server/sv.json | 24 +- .../Localization/Server/vi.json | 20 +- .../Localization/Server/zh_TW.json | 16 +- .../ServerManager/ServerManager.cs | 7 +- .../ServerManager/WebSocketConnection.cs | 54 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 22 +- MediaBrowser.ServerApplication/MainStartup.cs | 8 +- .../Native/RegisterServer.bat | 6 - .../Native/ServerAuthorization.cs | 8 +- .../Providers/MovieDbProviderTests.cs | 7 - MediaBrowser.Tests/Resolvers/TvUtilTests.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- .../MediaBrowser.WebDashboard.csproj | 15 +- 73 files changed, 1762 insertions(+), 1360 deletions(-) (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 993b69601c..768e9a4a82 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -157,7 +157,8 @@ namespace MediaBrowser.Api MetadataRefreshMode = request.MetadataRefreshMode, ImageRefreshMode = request.ImageRefreshMode, ReplaceAllImages = request.ReplaceAllImages, - ReplaceAllMetadata = request.ReplaceAllMetadata + ReplaceAllMetadata = request.ReplaceAllMetadata, + ForceSave = true }; } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index a098d26daa..f01d8ddf4b 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -65,6 +65,11 @@ namespace MediaBrowser.Api.Playback.Hls throw new ArgumentException("Video codec copy is not allowed here."); } + if (string.IsNullOrEmpty(request.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required"); + } + var result = GetAsync(request).Result; return result; diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index 178b840a87..850309465e 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -211,6 +211,8 @@ namespace MediaBrowser.Common.Implementations JsonSerializer = CreateJsonSerializer(); Logger = LogManager.GetLogger("App"); + OnLoggerLoaded(true); + LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false); IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted; progress.Report(2); @@ -219,16 +221,11 @@ namespace MediaBrowser.Common.Implementations ? LogSeverity.Debug : LogSeverity.Info; - // Put the app config in the log for troubleshooting purposes - Logger.LogMultiline("Application Configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration))); - progress.Report(3); DiscoverTypes(); progress.Report(14); - Logger.Info("Version {0} initializing", ApplicationVersion); - SetHttpLimit(); progress.Report(15); @@ -245,6 +242,47 @@ namespace MediaBrowser.Common.Implementations progress.Report(100); } + protected virtual void OnLoggerLoaded(bool isFirstLoad) + { + Logger.Info("Application version: {0}", ApplicationVersion); + + if (!isFirstLoad) + { + LogEnvironmentInfo(Logger, ApplicationPaths); + } + + // Put the app config in the log for troubleshooting purposes + Logger.LogMultiline("Application configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration))); + + if (Plugins != null) + { + var pluginBuilder = new StringBuilder(); + + foreach (var plugin in Plugins) + { + pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version)); + } + + Logger.LogMultiline("Plugins:", LogSeverity.Info, pluginBuilder); + } + } + + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) + { + logger.Info("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs())); + + logger.Info("Server: {0}", Environment.MachineName); + logger.Info("Operating system: {0}", Environment.OSVersion.ToString()); + logger.Info("Processor count: {0}", Environment.ProcessorCount); + logger.Info("64-Bit OS: {0}", Environment.Is64BitOperatingSystem); + logger.Info("64-Bit Process: {0}", Environment.Is64BitProcess); + logger.Info("Program data path: {0}", appPaths.ProgramDataPath); + + logger.Info("Application Path: {0}", appPaths.ApplicationPath); + + logger.Info("*** When reporting issues please include the entire log file. ***".ToUpper()); + } + protected virtual IJsonSerializer CreateJsonSerializer() { return new JsonSerializer(); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs index 78f60632fa..38b6b4ad68 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks public bool IsHidden { - get { return true; } + get { return false; } } public bool IsEnabled diff --git a/MediaBrowser.Common/Net/IWebSocket.cs b/MediaBrowser.Common/Net/IWebSocket.cs index 05f7975bca..b31a953192 100644 --- a/MediaBrowser.Common/Net/IWebSocket.cs +++ b/MediaBrowser.Common/Net/IWebSocket.cs @@ -32,15 +32,23 @@ namespace MediaBrowser.Common.Net /// /// The on receive. Action OnReceive { get; set; } - + /// /// Sends the async. /// /// The bytes. - /// The type. /// if set to true [end of message]. /// The cancellation token. /// Task. - Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken); + Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken); + + /// + /// Sends the asynchronous. + /// + /// The text. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Common/Net/IWebSocketConnection.cs b/MediaBrowser.Common/Net/IWebSocketConnection.cs index 9f9dfaeca3..b7715e20b9 100644 --- a/MediaBrowser.Common/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Common/Net/IWebSocketConnection.cs @@ -63,11 +63,10 @@ namespace MediaBrowser.Common.Net /// /// Sends a message asynchronously. /// - /// The buffer. - /// The type. + /// The text. /// The cancellation token. /// Task. /// buffer - Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken); + Task SendAsync(string text, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 88eadda005..af0ff8319e 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -200,7 +200,9 @@ namespace MediaBrowser.Controller.Library /// Determines whether [is series folder] [the specified path]. /// /// The path. + /// if set to true [consider seasonless series]. /// The file system children. + /// The directory service. /// true if [is series folder] [the specified path]; otherwise, false. public static bool IsSeriesFolder(string path, bool considerSeasonlessSeries, IEnumerable fileSystemChildren, IDirectoryService directoryService) { @@ -227,8 +229,10 @@ namespace MediaBrowser.Controller.Library { return true; } - - nonSeriesFolders++; + if (!EntityResolutionHelper.IgnoreFolders.Contains(child.Name, StringComparer.OrdinalIgnoreCase)) + { + nonSeriesFolders++; + } if (nonSeriesFolders >= 3) { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 904c7bcddf..3448a2905f 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -34,12 +34,6 @@ namespace MediaBrowser.Model.Configuration /// The HTTP server port number. public int HttpServerPortNumber { get; set; } - /// - /// Gets or sets the legacy web socket port number. - /// - /// The legacy web socket port number. - public int LegacyWebSocketPortNumber { get; set; } - /// /// Gets or sets a value indicating whether [enable internet providers]. /// @@ -216,7 +210,6 @@ namespace MediaBrowser.Model.Configuration MediaEncodingQuality = EncodingQuality.Auto; ImageSavingConvention = ImageSavingConvention.Compatible; HttpServerPortNumber = 8096; - LegacyWebSocketPortNumber = 8945; EnableDashboardResponseCaching = true; EnableAutomaticRestart = true; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 62a43751db..f530564eaa 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -157,7 +157,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); _listener = NativeWebSocket.IsSupported - ? _listener = new HttpListenerServer(_logger, _threadPoolManager) + ? _listener = new WebSocketSharpListener(_logger, _threadPoolManager) : _listener = new WebSocketSharpListener(_logger, _threadPoolManager); _listener.WebSocketHandler = WebSocketHandler; diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs index f89cdac47c..c7669fecb5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Events; +using System.Text; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; using System; @@ -36,7 +37,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The socket. /// The logger. /// socket - public NativeWebSocket(System.Net.WebSockets.WebSocket socket, ILogger logger) + public NativeWebSocket(WebSocket socket, ILogger logger) { if (socket == null) { @@ -155,6 +156,22 @@ namespace MediaBrowser.Server.Implementations.HttpServer return WebSocket.SendAsync(new ArraySegment(bytes), nativeType, true, linkedTokenSource.Token); } + public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) + { + var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); + + return WebSocket.SendAsync(new ArraySegment(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token); + } + + public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) + { + var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); + + var bytes = Encoding.UTF8.GetBytes(text); + + return WebSocket.SendAsync(new ArraySegment(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token); + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index 4127892409..7ff3a12478 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -96,22 +96,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp /// Sends the async. /// /// The bytes. - /// The type. /// if set to true [end of message]. /// The cancellation token. /// Task. - public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken) + public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - System.Net.WebSockets.WebSocketMessageType nativeType; + var completionSource = new TaskCompletionSource(); - if (!Enum.TryParse(type.ToString(), true, out nativeType)) - { - _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString()); - } + WebSocket.SendAsync(bytes, res => completionSource.TrySetResult(true)); + + return completionSource.Task; + } + /// + /// Sends the asynchronous. + /// + /// The text. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) + { var completionSource = new TaskCompletionSource(); - WebSocket.SendAsync(Encoding.UTF8.GetString(bytes), res => completionSource.TrySetResult(true)); + WebSocket.SendAsync(text, res => completionSource.TrySetResult(true)); return completionSource.Task; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 6b376d3b46..a756dc7944 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -67,14 +67,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { return null; } - - // Without these movies that have the name season in them could cause the parent folder to be resolved as a series - if (filename.IndexOf("[tmdbid=", StringComparison.OrdinalIgnoreCase) != -1) - { - return null; - } - if (args.ContainsMetaFileByName("series.xml") || filename.IndexOf("[tvdbid=", StringComparison.OrdinalIgnoreCase) != -1 || TVUtils.IsSeriesFolder(args.Path, collectionType == CollectionType.TvShows, args.FileSystemChildren, args.DirectoryService)) + if (TVUtils.IsSeriesFolder(args.Path, collectionType == CollectionType.TvShows, args.FileSystemChildren, args.DirectoryService)) { return new Series(); } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index eb1334c10d..1192bafb77 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json index f317188931..37a5735e4b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json index a7f7141754..92f352ca62 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -58,7 +58,7 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "P\u0159ehr\u00e1t", "ButtonEdit": "Upravit", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Titulky", "ButtonScenes": "Sc\u00e9ny", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Vybrat", + "ButtonNew": "Nov\u00e9" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json index 2b70bf74ed..988b036509 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Afspil", "ButtonEdit": "Rediger", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Undertekster", "ButtonScenes": "Scener", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "V\u00e6lg", + "ButtonNew": "Ny" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json index be4cf1bbfa..58b273c266 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Untertitel", "ButtonScenes": "Szenen", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Ausw\u00e4hlen", + "ButtonNew": "Neu" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json index a033dcbc9e..9ad65edbda 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json index 59dbb63e68..03694cad0a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json index a0fc056bc4..bfd93b7950 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json index 5c199c8ea1..05647f10d5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subt\u00edtulos", "ButtonScenes": "Escenas", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Seleccionar", + "ButtonNew": "Nuevo" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json index 1ca3aa56d7..62ecc25789 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -233,85 +233,89 @@ "ButtonRevoke": "Revocar", "MessageConfirmRevokeApiKey": "\u00bfEst\u00e1 seguro de querer revocar esta llave de API?", "HeaderConfirmRevokeApiKey": "Revocar llave de API", - "ValueContainer": "Container: {0}", - "ValueAudioCodec": "Audio Codec: {0}", - "ValueVideoCodec": "Video Codec: {0}", - "ValueCodec": "Codec: {0}", - "ValueConditions": "Conditions: {0}", - "LabelAll": "All", - "HeaderDeleteImage": "Delete Image", - "MessageFileNotFound": "File not found.", - "MessageFileReadError": "An error occurred reading this file.", - "ButtonNextPage": "Next Page", - "ButtonPreviousPage": "Previous Page", - "ButtonMoveLeft": "Move left", - "ButtonMoveRight": "Move right", - "ButtonBrowseOnlineImages": "Browse online images", - "HeaderDeleteItem": "Delete Item", - "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?", - "MessagePleaseEnterNameOrId": "Please enter a name or an external Id.", - "MessageValueNotCorrect": "The value entered is not correct. Please try again.", - "MessageItemSaved": "Item saved.", + "ValueContainer": "Contenedor: {0}", + "ValueAudioCodec": "C\u00f3dec de Audio: {0}", + "ValueVideoCodec": "C\u00f3dec de Video: {0}", + "ValueCodec": "C\u00f3dec: {0}", + "ValueConditions": "Condiciones: {0}", + "LabelAll": "Todos", + "HeaderDeleteImage": "Eliminar Im\u00e1gen", + "MessageFileNotFound": "Archivo no encontrado.", + "MessageFileReadError": "Ha ocurrido un error al leer este archivo.", + "ButtonNextPage": "P\u00e1gina Siguiente", + "ButtonPreviousPage": "P\u00e1gina Anterior", + "ButtonMoveLeft": "Mover a la izquierda", + "ButtonMoveRight": "Mover a la derecha", + "ButtonBrowseOnlineImages": "Navegar por im\u00e1genes en l\u00ednea", + "HeaderDeleteItem": "Eliminar \u00cdtem", + "ConfirmDeleteItem": "\u00bfEsta seguro de querer eleiminar este \u00edtem de su biblioteca?", + "MessagePleaseEnterNameOrId": "Por favor ingrese un nombre o id externo.", + "MessageValueNotCorrect": "El valor ingresado no es correcto. Intente nuevamente por favor.", + "MessageItemSaved": "\u00cdtem guardado.", "OptionEnded": "Finalizado", "OptionContinuing": "Continuando", "OptionOff": "No", "OptionOn": "Si", - "HeaderFields": "Fields", - "HeaderFieldsHelp": "Slide a field to 'off' to lock it and prevent it's data from being changed.", - "HeaderLiveTV": "Live TV", - "MissingLocalTrailer": "Missing local trailer.", - "MissingPrimaryImage": "Missing primary image.", - "MissingBackdropImage": "Missing backdrop image.", - "MissingLogoImage": "Missing logo image.", - "MissingEpisode": "Missing episode.", - "OptionScreenshots": "Screenshots", - "OptionBackdrops": "Backdrops", - "OptionImages": "Images", - "OptionKeywords": "Keywords", - "OptionTags": "Tags", - "OptionStudios": "Studios", - "OptionName": "Name", - "OptionOverview": "Overview", - "OptionGenres": "Genres", + "HeaderFields": "Campos", + "HeaderFieldsHelp": "Deslice un campo hacia \"apagado\" para bloquearlo y evitar que sus datos sean modificados.", + "HeaderLiveTV": "TV en Vivo", + "MissingLocalTrailer": "Falta avance local.", + "MissingPrimaryImage": "Falta im\u00e1gen primaria.", + "MissingBackdropImage": "Falta im\u00e1gen de fondo.", + "MissingLogoImage": "Falta im\u00e1gen de logo.", + "MissingEpisode": "Falta episodio.", + "OptionScreenshots": "Capuras de Pantalla", + "OptionBackdrops": "Fondos", + "OptionImages": "Im\u00e1genes", + "OptionKeywords": "Palabras clave", + "OptionTags": "Etiquetas", + "OptionStudios": "Estudios", + "OptionName": "Nombre", + "OptionOverview": "Sinopsis", + "OptionGenres": "G\u00e9neros", "OptionParentalRating": "Clasificaci\u00f3n Parental", - "OptionPeople": "People", + "OptionPeople": "Personas", "OptionRuntime": "Duraci\u00f3n", - "OptionProductionLocations": "Production Locations", - "OptionBirthLocation": "Birth Location", + "OptionProductionLocations": "Lugares de Producci\u00f3n", + "OptionBirthLocation": "Lugar de Nacimiento", "LabelAllChannels": "Todos los canales", - "LabelLiveProgram": "LIVE", - "LabelNewProgram": "NEW", - "LabelPremiereProgram": "PREMIERE", - "HeaderChangeFolderType": "Change Folder Type", - "HeaderChangeFolderTypeHelp": "To change the folder type, please remove and rebuild the collection with the new type.", - "HeaderAlert": "Alert", - "MessagePleaseRestart": "Please restart to finish updating.", + "LabelLiveProgram": "EN VIVO", + "LabelNewProgram": "NUEVO", + "LabelPremiereProgram": "ESTRENO", + "HeaderChangeFolderType": "Cambiar tipo de carpeta", + "HeaderChangeFolderTypeHelp": "Para cambiar el tipo de carpeta, por favor elimine y reconstruya la colecci\u00f3n con el nuevo tipo.", + "HeaderAlert": "Alerta", + "MessagePleaseRestart": "Por favor reinicie para finalizar la actualizaci\u00f3n.", "ButtonRestart": "Reiniciar", - "MessagePleaseRefreshPage": "Please refresh this page to receive new updates from the server.", - "ButtonHide": "Hide", - "MessageSettingsSaved": "Settings saved.", - "ButtonSignOut": "Sign Out", - "ButtonMyProfile": "My Profile", - "ButtonMyPreferences": "My Preferences", - "MessageBrowserDoesNotSupportWebSockets": "This browser does not support web sockets. For a better experience, try a newer browser such as Chrome, Firefox, IE10+, Safari (iOS) or Opera.", - "LabelInstallingPackage": "Installing {0}", - "LabelPackageInstallCompleted": "{0} installation completed.", - "LabelPackageInstallFailed": "{0} installation failed.", - "LabelPackageInstallCancelled": "{0} installation cancelled.", + "MessagePleaseRefreshPage": "Por favor actualice esta p\u00e1gina para recibir nuevas actualizaciones desde el servidor.", + "ButtonHide": "Ocultar", + "MessageSettingsSaved": "Configuraciones guardadas", + "ButtonSignOut": "Cerrar Sesi\u00f3n", + "ButtonMyProfile": "Mi Perf\u00edl", + "ButtonMyPreferences": "Mis Preferencias", + "MessageBrowserDoesNotSupportWebSockets": "Este navegador no soporta sockets web. Para una mejor experiencia, pruebe con un navegador m\u00e1s nuevo como Chrome, Firefox, IE10+, Safari (iOS) u Opera.", + "LabelInstallingPackage": "Instalando {0}", + "LabelPackageInstallCompleted": "{0} instalaci\u00f3n completada.", + "LabelPackageInstallFailed": "{0} instalaci\u00f3n fallida.", + "LabelPackageInstallCancelled": "{0} instalaci\u00f3n cancelada.", "TabServer": "Servidor", - "TabUsers": "Users", - "TabLibrary": "Library", + "TabUsers": "Usuarios", + "TabLibrary": "Biblioteca", "TabMetadata": "Metadatos", "TabDLNA": "DLNA", - "TabLiveTV": "Live TV", - "TabAutoOrganize": "Auto-Organize", - "TabPlugins": "Plugins", + "TabLiveTV": "TV en Vivo", + "TabAutoOrganize": "Auto-Organizar", + "TabPlugins": "Complementos", "TabAdvanced": "Avanzado", - "TabHelp": "Help", - "TabScheduledTasks": "Scheduled Tasks", - "ButtonFullscreen": "Fullscreen", - "ButtonAudioTracks": "Audio Tracks", + "TabHelp": "Ayuda", + "TabScheduledTasks": "Tareas Programadas", + "ButtonFullscreen": "Pantalla completa", + "ButtonAudioTracks": "Pistas de Sonido", "ButtonSubtitles": "Subt\u00edtulos", "ButtonScenes": "Escenas", - "ButtonQuality": "Quality" + "ButtonQuality": "Calidad", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Seleccionar", + "ButtonNew": "Nuevo" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json index 0919afc7d7..e0b199b12f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Sous-titres", "ButtonScenes": "Sc\u00e8nes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "S\u00e9lectionner", + "ButtonNew": "Nouveau" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json index 6310881f20..2cb25ef9b6 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "\u05e0\u05d2\u05df", "ButtonEdit": "\u05e2\u05e8\u05d5\u05da", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "\u05d1\u05d7\u05e8", + "ButtonNew": "\u05d7\u05d3\u05e9" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json index 96e71a9717..1963f249af 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -216,31 +216,31 @@ "HeaderAlbumArtist": "Artista Album", "HeaderArtist": "Artista", "LabelAddedOnDate": "Added {0}", - "ButtonStart": "Start", + "ButtonStart": "Avvio", "HeaderChannels": "Canali", "HeaderMediaFolders": "Cartelle dei media", - "HeaderBlockItemsWithNoRating": "Block items with no rating information:", - "OptionBlockOthers": "Others", - "OptionBlockTvShows": "TV Shows", - "OptionBlockTrailers": "Trailers", - "OptionBlockMusic": "Music", - "OptionBlockMovies": "Movies", - "OptionBlockBooks": "Books", - "OptionBlockGames": "Games", - "OptionBlockLiveTvPrograms": "Live TV Programs", - "OptionBlockLiveTvChannels": "Live TV Channels", - "OptionBlockChannelContent": "Internet Channel Content", - "ButtonRevoke": "Revoke", - "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Media Browser will be abruptly terminated.", - "HeaderConfirmRevokeApiKey": "Revoke Api Key", + "HeaderBlockItemsWithNoRating": "Blocca gli elementi senza informazioni di valutazione", + "OptionBlockOthers": "Altri", + "OptionBlockTvShows": "Serie TV", + "OptionBlockTrailers": "Trailer", + "OptionBlockMusic": "Musica", + "OptionBlockMovies": "Film", + "OptionBlockBooks": "Libri", + "OptionBlockGames": "Giochi", + "OptionBlockLiveTvPrograms": "Programmi TV in onda", + "OptionBlockLiveTvChannels": "Canali TV in onda", + "OptionBlockChannelContent": "Contenuto di Canali Internet", + "ButtonRevoke": "Revocare", + "MessageConfirmRevokeApiKey": "Sei sicuro che desideri revocare le chiavi api? La connessione dell'applicazione con Media Browser sar\u00e0 improvvisamente terminata.", + "HeaderConfirmRevokeApiKey": "Revocare Chiave Api", "ValueContainer": "Container: {0}", "ValueAudioCodec": "Audio Codec: {0}", "ValueVideoCodec": "Video Codec: {0}", "ValueCodec": "Codec: {0}", "ValueConditions": "Conditions: {0}", - "LabelAll": "All", - "HeaderDeleteImage": "Delete Image", - "MessageFileNotFound": "File not found.", + "LabelAll": "Tutti", + "HeaderDeleteImage": "Cancella Immagine", + "MessageFileNotFound": "File non trovato.", "MessageFileReadError": "An error occurred reading this file.", "ButtonNextPage": "Next Page", "ButtonPreviousPage": "Previous Page", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Sottotitoli", "ButtonScenes": "Scene", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Seleziona", + "ButtonNew": "Nuovo" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 187bcb59f0..0b6e7c9c5c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -296,29 +296,32 @@ "MessageSettingsSaved": "Settings saved.", "ButtonSignOut": "Sign Out", "ButtonMyProfile": "My Profile", - "ButtonMyPreferences": "My Preferences", - "MessageBrowserDoesNotSupportWebSockets": "This browser does not support web sockets. For a better experience, try a newer browser such as Chrome, Firefox, IE10+, Safari (iOS) or Opera.", - "LabelInstallingPackage": "Installing {0}", - "LabelPackageInstallCompleted": "{0} installation completed.", - "LabelPackageInstallFailed": "{0} installation failed.", - "LabelPackageInstallCancelled": "{0} installation cancelled.", - "TabServer": "Server", - "TabUsers": "Users", - "TabLibrary": "Library", - "TabMetadata": "Metadata", - "TabDLNA": "DLNA", - "TabLiveTV": "Live TV", - "TabAutoOrganize": "Auto-Organize", - "TabPlugins": "Plugins", - "TabAdvanced": "Advanced", - "TabHelp": "Help", - "TabScheduledTasks": "Scheduled Tasks", - "ButtonFullscreen": "Fullscreen", - "ButtonMute": "Mute", - "ButtonUnmute": "Unmute", - "ButtonAudioTracks": "Audio Tracks", - "ButtonSubtitles": "Subtitles", - "ButtonScenes": "Scenes", - "ButtonQuality": "Quality", - "HeaderNotifications": "Notifications" + "ButtonMyPreferences": "My Preferences", + "MessageBrowserDoesNotSupportWebSockets": "This browser does not support web sockets. For a better experience, try a newer browser such as Chrome, Firefox, IE10+, Safari (iOS) or Opera.", + "LabelInstallingPackage": "Installing {0}", + "LabelPackageInstallCompleted": "{0} installation completed.", + "LabelPackageInstallFailed": "{0} installation failed.", + "LabelPackageInstallCancelled": "{0} installation cancelled.", + "TabServer": "Server", + "TabUsers": "Users", + "TabLibrary": "Library", + "TabMetadata": "Metadata", + "TabDLNA": "DLNA", + "TabLiveTV": "Live TV", + "TabAutoOrganize": "Auto-Organize", + "TabPlugins": "Plugins", + "TabAdvanced": "Advanced", + "TabHelp": "Help", + "TabScheduledTasks": "Scheduled Tasks", + "ButtonFullscreen": "Fullscreen", + "ButtonMute": "Mute", + "ButtonUnmute": "Unmute", + "ButtonAudioTracks": "Audio Tracks", + "ButtonSubtitles": "Subtitles", + "ButtonScenes": "Scenes", + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json index a15a128782..113389a11f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "\u0414\u044b\u0431\u044b\u0441 \u0436\u043e\u043b\u0448\u044b\u0493\u044b", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440", "ButtonScenes": "\u0421\u0430\u0445\u043d\u0430\u043b\u0430\u0440", - "ButtonQuality": "\u0421\u0430\u043f\u0430" + "ButtonQuality": "\u0421\u0430\u043f\u0430", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "\u0411\u04e9\u043b\u0435\u043a\u0442\u0435\u0443", + "ButtonNew": "\u0416\u0430\u0441\u0430\u0443" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json index 30a6b8e261..3ed0c8735c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json index 6f726759dd..c1700fe519 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json index 3f0d010842..10275847da 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -20,11 +20,11 @@ "OptionRelease": "Offici\u00eble Release", "OptionBeta": "Beta", "OptionDev": "Dev (Instabiel)", - "UninstallPluginHeader": "Plug-in de\u00efnstalleren", + "UninstallPluginHeader": "Invoegtoepassing de\u00efnstalleren", "UninstallPluginConfirmation": "Weet u zeker dat u {0} wilt de\u00efnstalleren?", - "NoPluginConfigurationMessage": "Deze plug-in heeft niets in te stellen", - "NoPluginsInstalledMessage": "U heeft geen plug-ins ge\u00efnstalleerd", - "BrowsePluginCatalogMessage": "Bekijk de Plug-in catalogus voor beschikbare plug-ins.", + "NoPluginConfigurationMessage": "Deze Invoegtoepassing heeft niets in te stellen", + "NoPluginsInstalledMessage": "U heeft geen Invoegtoepassingen ge\u00efnstalleerd", + "BrowsePluginCatalogMessage": "Bekijk de Invoegtoepassings catalogus voor beschikbare Invoegtoepassingen.", "MessageKeyEmailedTo": "Sleutel gemaild naar {0}.", "MessageKeysLinked": "Sleutels gekoppeld.", "HeaderConfirmation": "Bevestiging", @@ -45,7 +45,7 @@ "HeaderDeleteTaskTrigger": "Verwijderen Taak Trigger", "HeaderTaskTriggers": "Taak Triggers", "MessageDeleteTaskTrigger": "Weet u zeker dat u deze taak trigger wilt verwijderen?", - "MessageNoPluginsInstalled": "U heeft geen plug-ins ge\u00efnstalleerd.", + "MessageNoPluginsInstalled": "U heeft geen Invoegtoepassingen ge\u00efnstalleerd.", "LabelVersionInstalled": "{0} ge\u00efnstalleerd", "LabelNumberReviews": "{0} Recensies", "LabelFree": "Gratis", @@ -305,7 +305,7 @@ "TabDLNA": "DLNA", "TabLiveTV": "Live TV", "TabAutoOrganize": "Automatisch-Organiseren", - "TabPlugins": "Plugins", + "TabPlugins": "Invoegtoepassingen", "TabAdvanced": "Geavanceerd", "TabHelp": "Hulp", "TabScheduledTasks": "Geplande taken", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Ondertitels", "ButtonScenes": "Scenes", - "ButtonQuality": "Kwaliteit" + "ButtonQuality": "Kwaliteit", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Selecteer", + "ButtonNew": "Nieuw" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json index b97935ddeb..3375c1a1d8 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Select", + "ButtonNew": "New" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json index 367e971d8f..aac6a2c05a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Faixas de \u00c1udio", "ButtonSubtitles": "Legendas", "ButtonScenes": "Cenas", - "ButtonQuality": "Qualidade" + "ButtonQuality": "Qualidade", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Selecionar", + "ButtonNew": "Nova" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json index c7f7c0f69f..eb3740b84c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Legendas", "ButtonScenes": "Cenas", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "Selecionar", + "ButtonNew": "Novo" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json index a7d6690f04..ea32fb9329 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -68,7 +68,7 @@ "ButtonPreviousTrack": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "LabelEnabled": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "LabelDisabled": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", - "ButtonMoreInformation": "\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u0435 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044f", + "ButtonMoreInformation": "\u0415\u0449\u0451 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044f", "LabelNoUnreadNotifications": "\u041d\u0435\u0442 \u043d\u0435\u043f\u0440\u043e\u0447\u0442\u0451\u043d\u043d\u044b\u0445 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.", "ButtonViewNotifications": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f", "ButtonMarkTheseRead": "\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0430\u043a \u043f\u0440\u043e\u0447\u0442\u0451\u043d\u043d\u044b\u0435", @@ -207,7 +207,7 @@ "MessageErrorPlayingVideo": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u0432\u0438\u0434\u0435\u043e.", "MessageEnsureOpenTuner": "\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0442\u0430\u043c \u043e\u0442\u043a\u0440\u044b\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u0442\u044e\u043d\u0435\u0440.", "ButtonHome": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f", - "ButtonDashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430", + "ButtonDashboard": "\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c", "ButtonReports": "\u041e\u0442\u0447\u0451\u0442\u044b", "ButtonMetadataManager": "\u0414\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445", "HeaderTime": "\u0412\u0440\u0435\u043c\u044f", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "\u0410\u0443\u0434\u0438\u043e \u0434\u043e\u0440\u043e\u0436\u043a\u0438", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b", "ButtonScenes": "\u0421\u0446\u0435\u043d\u044b", - "ButtonQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e" + "ButtonQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c", + "ButtonNew": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json index a0ee4fdaf7..421a0dea9a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Undertexter", "ButtonScenes": "Scener", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "V\u00e4lj", + "ButtonNew": "Nytillkommet" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json index b9b21645b1..0f93d06956 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "L\u1ef1a ch\u1ecdn", + "ButtonNew": "M\u1edbi" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json index 5408f0b915..fe7d379946 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -58,14 +58,14 @@ "ButtonMute": "Mute", "ButtonUnmute": "Unmute", "ButtonStop": "Stop", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "\u64ad\u653e", "ButtonEdit": "\u7de8\u8f2f", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -313,5 +313,9 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications", + "HeaderSelectPlayer": "Select Player:", + "ButtonSelect": "\u9078\u64c7", + "ButtonNew": "\u5275\u5efa" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ar.json b/MediaBrowser.Server.Implementations/Localization/Server/ar.json index 59438e2b5e..df4178c6a4 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ca.json b/MediaBrowser.Server.Implementations/Localization/Server/ca.json index 1de1534e7a..461e56f49e 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/cs.json b/MediaBrowser.Server.Implementations/Localization/Server/cs.json index 507045cbbf..d6cdfe0285 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -199,6 +199,7 @@ "OptionFriday": "P\u00e1tek", "OptionSaturday": "Sobota", "HeaderManagement": "Management:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Chyb\u011bj\u00edc\u00ed IMDb Id", "OptionMissingTvdbId": "Chyb\u011bj\u00edc\u00ed TheTVDB Id", "OptionMissingOverview": "Chyb\u011bj\u00edc\u00ed p\u0159ehled", @@ -479,7 +480,7 @@ "HeaderProgram": "Program", "HeaderClients": "Klienti", "LabelCompleted": "Hotovo", - "LabelFailed": "Chyba", + "LabelFailed": "Failed", "LabelSkipped": "P\u0159esko\u010deno", "HeaderEpisodeOrganization": "Organizace epizod", "LabelSeries": "Series:", @@ -629,8 +630,8 @@ "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Sc\u00e9ny", "ButtonSubtitles": "Titulky", - "ButtonAudioTracks": "Audio stopy", - "ButtonPreviousTrack": "P\u0159edchod\u00ed stopa", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pause", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/da.json b/MediaBrowser.Server.Implementations/Localization/Server/da.json index 55c296948c..8db9c68f89 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -199,6 +199,7 @@ "OptionFriday": "Fredag", "OptionSaturday": "L\u00f8rdag", "HeaderManagement": "Ledelse:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Manglende IMDB Id", "OptionMissingTvdbId": "Manglende TheTVDB Id", "OptionMissingOverview": "Manglende Overblik", @@ -626,10 +627,10 @@ "TabNowPlaying": "Spiler nu", "TabNavigation": "Navigation", "TabControls": "Controls", - "ButtonFullscreen": "Skift til fuldsk\u00e6rm", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Scener", "ButtonSubtitles": "Undertekster", - "ButtonAudioTracks": "Lyd filer", + "ButtonAudioTracks": "Audio tracks", "ButtonPreviousTrack": "Previous track", "ButtonNextTrack": "Next track", "ButtonStop": "Stop", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json index cfb0924d22..90c4c9e529 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -199,6 +199,7 @@ "OptionFriday": "Freitag", "OptionSaturday": "Samstag", "HeaderManagement": "Verwaltung:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Fehlende IMDb Id", "OptionMissingTvdbId": "Fehlende TheTVDB Id", "OptionMissingOverview": "Fehlende \u00dcbersicht", @@ -479,10 +480,10 @@ "HeaderProgram": "Programm", "HeaderClients": "Clients", "LabelCompleted": "Fertiggestellt", - "LabelFailed": "Gescheitert", + "LabelFailed": "Failed", "LabelSkipped": "\u00dcbersprungen", "HeaderEpisodeOrganization": "Episodensortierung", - "LabelSeries": "Serien:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Staffelnummer", "LabelEpisodeNumber": "Episodennummer", "LabelEndingEpisodeNumber": "Ending episode number", @@ -626,12 +627,12 @@ "TabNowPlaying": "Aktuelle Wiedergabe", "TabNavigation": "Navigation", "TabControls": "Controls", - "ButtonFullscreen": "Schalte Vollbild um", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Szenen", "ButtonSubtitles": "Untertitel", - "ButtonAudioTracks": "Audiospuren", - "ButtonPreviousTrack": "Vorheriger Track", - "ButtonNextTrack": "N\u00e4chster Track", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/el.json b/MediaBrowser.Server.Implementations/Localization/Server/el.json index b6c94c512f..b778dc4154 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json index 48a5cc0049..6538111571 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json index 16700e356d..f315798ec9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json index 45b3b5c781..18e44a03cf 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json @@ -199,6 +199,7 @@ "OptionFriday": "Viernes", "OptionSaturday": "S\u00e1bado", "HeaderManagement": "Administraci\u00f3n", + "LabelManagement": "Management:", "OptionMissingImdbId": "Falta IMDb Id", "OptionMissingTvdbId": "Falta TheTVDB Id", "OptionMissingOverview": "Falta argumento", @@ -479,10 +480,10 @@ "HeaderProgram": "Programa", "HeaderClients": "Clientes", "LabelCompleted": "Completado", - "LabelFailed": "Err\u00f3neo", + "LabelFailed": "Error", "LabelSkipped": "Omitido", "HeaderEpisodeOrganization": "Organizaci\u00f3n de episodios", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Temporada n\u00famero:", "LabelEpisodeNumber": "Episodio n\u00famero:", "LabelEndingEpisodeNumber": "N\u00famero episodio final:", @@ -626,10 +627,10 @@ "TabNowPlaying": "Reproduciendo ahora", "TabNavigation": "Navegaci\u00f3n", "TabControls": "Controles", - "ButtonFullscreen": "Pantalla completa", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Escenas", "ButtonSubtitles": "Subt\u00edtulos", - "ButtonAudioTracks": "Pistas de audio", + "ButtonAudioTracks": "Audio tracks", "ButtonPreviousTrack": "Pista anterior", "ButtonNextTrack": "Pista siguiente", "ButtonStop": "Detener", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json index 733e377065..55439632f5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -199,6 +199,7 @@ "OptionFriday": "Viernes", "OptionSaturday": "S\u00e1bado", "HeaderManagement": "Administraci\u00f3n:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Falta Id de IMDb", "OptionMissingTvdbId": "Falta Id de TheTVDB", "OptionMissingOverview": "Falta Sinopsis", @@ -626,12 +627,12 @@ "TabNowPlaying": "Reproduci\u00e9ndo Ahora", "TabNavigation": "Navegaci\u00f3n", "TabControls": "Controles", - "ButtonFullscreen": "Alternar pantalla completa", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Escenas", "ButtonSubtitles": "Subt\u00edtulos", - "ButtonAudioTracks": "Pistas de audio", - "ButtonPreviousTrack": "Pista Anterior", - "ButtonNextTrack": "Pista Siguiente", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Pista anterior", + "ButtonNextTrack": "Pista siguiente", "ButtonStop": "Detener", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Agrupar pel\u00edculas en colecciones", @@ -868,11 +869,20 @@ "LabelAppName": "Nombre del App", "LabelAppNameExample": "Ejemplo: Sickbeard, NzbDrone", "HeaderNewApiKeyHelp": "Otorgar a la aplicaci\u00f3n persmiso para comunicarse con Media Browser.", - "HeaderHttpHeaders": "Http Headers", - "HeaderIdentificationHeader": "Identification Header", - "LabelValue": "Value:", - "LabelMatchType": "Match type:", - "OptionEquals": "Equals", + "HeaderHttpHeaders": "Encabezados Http", + "HeaderIdentificationHeader": "Encabezado de Identificaci\u00f3n", + "LabelValue": "Valor:", + "LabelMatchType": "Tipo de Coincidencia:", + "OptionEquals": "Igual a", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Subcadena", + "TabView": "Vista", + "TabSort": "Ordenaci\u00f3n", + "TabFilter": "Filtro", + "ButtonView": "Vista", + "LabelPageSize": "Tama\u00f1o de Pantalla:", + "LabelView": "Vista:", + "TabUsers": "Usuarios", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json index ccfccea6ae..fc02a9e8ca 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -199,6 +199,7 @@ "OptionFriday": "Vendredi", "OptionSaturday": "Samedi", "HeaderManagement": "Gestion:", + "LabelManagement": "Management:", "OptionMissingImdbId": "ID IMDb manquant:", "OptionMissingTvdbId": "ID TVDB manquant:", "OptionMissingOverview": "R\u00e9sum\u00e9 manquant", @@ -396,7 +397,7 @@ "HeaderCastCrew": "\u00c9quipe de tournage", "HeaderAdditionalParts": "Parties Additionelles", "ButtonSplitVersionsApart": "S\u00e9parer les versions", - "ButtonPlayTrailer": "Bande-annonce", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "Manquant(s)", "LabelOffline": "Hors ligne", "PathSubstitutionHelp": "Les substitutions de chemins d'acc\u00e8s sont utilis\u00e9es pour faire correspondre un chemin d'acc\u00e8s du serveur \u00e0 un chemin d'acc\u00e8s accessible par les clients. En autorisant un acc\u00e8s direct aux m\u00e9dias du serveur, les clients pourront les lire directement du r\u00e9seau et \u00e9viter l'utilisation inutiles des ressources du serveur en demandant du transcodage.", @@ -479,10 +480,10 @@ "HeaderProgram": "Programme", "HeaderClients": "Clients", "LabelCompleted": "Compl\u00e9t\u00e9", - "LabelFailed": "\u00c9chec", + "LabelFailed": "Failed", "LabelSkipped": "Saut\u00e9", "HeaderEpisodeOrganization": "Organisation d'\u00e9pisodes", - "LabelSeries": "S\u00e9ries:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Num\u00e9ro de saison", "LabelEpisodeNumber": "Num\u00e9ro d'\u00e9pisode", "LabelEndingEpisodeNumber": "Num\u00e9ro d'\u00e9pisode se terminant", @@ -626,12 +627,12 @@ "TabNowPlaying": "En cours de lecture", "TabNavigation": "Navigation", "TabControls": "Contr\u00f4les", - "ButtonFullscreen": "Plein \u00e9cran", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Sc\u00e8nes", "ButtonSubtitles": "Sous-titres", - "ButtonAudioTracks": "Piste audio", - "ButtonPreviousTrack": "Piste pr\u00e9c\u00e9dante", - "ButtonNextTrack": "Piste suivante", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Arr\u00eat", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Grouper les films en collections", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/he.json b/MediaBrowser.Server.Implementations/Localization/Server/he.json index 473b2523fc..effeb0ad60 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -199,6 +199,7 @@ "OptionFriday": "\u05e9\u05d9\u05e9\u05d9", "OptionSaturday": "\u05e9\u05d1\u05ea", "HeaderManagement": "\u05e0\u05d9\u05d4\u05d5\u05dc", + "LabelManagement": "Management:", "OptionMissingImdbId": "\u05d7\u05e1\u05e8 \u05de\u05d6\u05d4\u05d4 IMBb", "OptionMissingTvdbId": "\u05d7\u05e1\u05e8 \u05de\u05d6\u05d4\u05d4 TheTVDB", "OptionMissingOverview": "\u05d7\u05e1\u05e8\u05d4 \u05e1\u05e7\u05d9\u05e8\u05d4", @@ -396,7 +397,7 @@ "HeaderCastCrew": "\u05e9\u05d7\u05e7\u05e0\u05d9\u05dd \u05d5\u05e6\u05d5\u05d5\u05ea", "HeaderAdditionalParts": "\u05d7\u05dc\u05e7\u05d9\u05dd \u05e0\u05d5\u05e1\u05e4\u05d9\u05dd", "ButtonSplitVersionsApart": "\u05e4\u05e6\u05dc \u05d2\u05e8\u05e1\u05d0\u05d5\u05ea \u05d1\u05e0\u05e4\u05e8\u05d3", - "ButtonPlayTrailer": "\u05d8\u05e8\u05d9\u05d9\u05dc\u05e8\u05d9\u05dd", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "\u05d7\u05e1\u05e8", "LabelOffline": "\u05dc\u05d0 \u05de\u05e7\u05d5\u05d5\u05df", "PathSubstitutionHelp": "\u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05d7\u05dc\u05d5\u05e4\u05d9\u05d9\u05dd \u05d4\u05dd \u05dc\u05e6\u05d5\u05e8\u05da \u05de\u05d9\u05e4\u05d5\u05d9 \u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05d1\u05e9\u05e8\u05ea \u05dc\u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05e9\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d9\u05db\u05d5\u05dc\u05d9\u05dd \u05dc\u05d2\u05e9\u05ea \u05d0\u05dc\u05d9\u05d4\u05dd. \u05e2\u05dc \u05d9\u05d3\u05d9 \u05d4\u05e8\u05e9\u05d0\u05d4 \u05dc\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d2\u05d9\u05e9\u05d4 \u05d9\u05e9\u05d9\u05e8\u05d4 \u05dc\u05de\u05d3\u05d9\u05d4 \u05d1\u05e9\u05e8\u05ea \u05d0\u05dd \u05d9\u05db\u05d5\u05dc\u05d9\u05dd \u05dc\u05e0\u05d2\u05df \u05d0\u05ea \u05d4\u05e7\u05d1\u05e6\u05d9\u05dd \u05d9\u05e9\u05d9\u05e8\u05d5\u05ea \u05e2\u05dc \u05d2\u05d1\u05d9 \u05d4\u05e8\u05e9\u05ea \u05d5\u05dc\u05d4\u05d9\u05de\u05e0\u05e2 \u05de\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05de\u05e9\u05d0\u05d1\u05d9 \u05d4\u05e9\u05e8\u05ea \u05dc\u05e6\u05d5\u05e8\u05da \u05e7\u05d9\u05d3\u05d5\u05d3 \u05d5\u05e9\u05d9\u05d3\u05d5\u05e8.", @@ -479,10 +480,10 @@ "HeaderProgram": "\u05ea\u05d5\u05db\u05e0\u05d4", "HeaderClients": "\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd", "LabelCompleted": "\u05d4\u05d5\u05e9\u05dc\u05dd", - "LabelFailed": "\u05e0\u05db\u05e9\u05dc", + "LabelFailed": "Failed", "LabelSkipped": "\u05d3\u05d5\u05dc\u05d2", "HeaderEpisodeOrganization": "\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05e4\u05e8\u05e7\u05d9\u05dd", - "LabelSeries": "\u05e1\u05d3\u05e8\u05d4:", + "LabelSeries": "Series:", "LabelSeasonNumber": "\u05de\u05e1\u05e4\u05e8 \u05e2\u05d5\u05e0\u05d4:", "LabelEpisodeNumber": "\u05de\u05e1\u05e4\u05e8 \u05e4\u05e8\u05e7:", "LabelEndingEpisodeNumber": "\u05de\u05e1\u05e4\u05e8 \u05e1\u05d9\u05d5\u05dd \u05e4\u05e8\u05e7:", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/it.json b/MediaBrowser.Server.Implementations/Localization/Server/it.json index 77d265bd48..2757333f1c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -5,9 +5,9 @@ "LabelSwagger": "Swagger", "LabelStandard": "Standard", "LabelViewApiDocumentation": "Documentazione Api", - "LabelBrowseLibrary": "Apri Media Browser", + "LabelBrowseLibrary": "Esplora la libreria", "LabelConfigureMediaBrowser": "Configura Media Browser", - "LabelOpenLibraryViewer": "Esplora la Libreria", + "LabelOpenLibraryViewer": "Apri visualizzatore libreria", "LabelRestartServer": "Riavvia Server", "LabelShowLogWindow": "Mostra Finestra log", "LabelPrevious": "Precedente", @@ -199,6 +199,7 @@ "OptionFriday": "Venerdi", "OptionSaturday": "Sabato", "HeaderManagement": "Gestione:", + "LabelManagement": "Management:", "OptionMissingImdbId": "IMDB id mancante", "OptionMissingTvdbId": "TheTVDB Id mancante", "OptionMissingOverview": "Trama mancante", @@ -479,10 +480,10 @@ "HeaderProgram": "Programma", "HeaderClients": "Dispositivi", "LabelCompleted": "Completato", - "LabelFailed": "Fallito", + "LabelFailed": "Failed", "LabelSkipped": "Saltato", "HeaderEpisodeOrganization": "Organizzazione Episodi", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Numero Stagione:", "LabelEpisodeNumber": "Numero Episodio:", "LabelEndingEpisodeNumber": "Ultimo Episodio Numero:", @@ -626,12 +627,12 @@ "TabNowPlaying": "In esecuzione", "TabNavigation": "Navigazione", "TabControls": "Controlli", - "ButtonFullscreen": "Schermo intero", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Scene", "ButtonSubtitles": "Sottotitoli", - "ButtonAudioTracks": "Traccia Audio", - "ButtonPreviousTrack": "Precedente", - "ButtonNextTrack": "Prossimo", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pausa", "LabelGroupMoviesIntoCollections": "Raggruppa i film nelle collection", @@ -846,33 +847,42 @@ "LabelLoginDisclaimerHelp": "Questo verr\u00e0 visualizzato nella parte inferiore della pagina di accesso.", "LabelAutomaticallyDonate": "Donare automaticamente questo importo ogni mese", "LabelAutomaticallyDonateHelp": "\u00c8 possibile annullare in qualsiasi momento tramite il vostro conto PayPal.", - "OptionList": "List", - "TabDashboard": "Dashboard", + "OptionList": "Lista", + "TabDashboard": "Pannello Controllo", "TitleServer": "Server", "LabelCache": "Cache:", - "LabelLogs": "Logs:", + "LabelLogs": "Log:", "LabelMetadata": "Metadata:", - "LabelImagesByName": "Images by name:", - "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", - "HeaderLatestMusic": "Latest Music", + "LabelImagesByName": "Immagini per nome:", + "LabelTranscodingTemporaryFiles": "transcodifica file temporanei:", + "HeaderLatestMusic": "Musica Recente", "HeaderBranding": "Branding", - "HeaderApiKeys": "Api Keys", - "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Media Browser. Keys are issued by logging in with a Media Browser account, or by manually granting the application a key.", - "HeaderApiKey": "Api Key", + "HeaderApiKeys": "Chiavi Api", + "HeaderApiKeysHelp": "Le applicazioni estrene richiedono una chiave Api per comunicare con Media Browser. Le chiavi sono create attraverso il log-in con un account Media Browse, o manualmente rilasciando una chiave all'applicazione.", + "HeaderApiKey": "Chiave Api", "HeaderApp": "App", - "HeaderDevice": "Device", - "HeaderUser": "User", - "HeaderDateIssued": "Date Issued", + "HeaderDevice": "Dispositivo", + "HeaderUser": "Utente", + "HeaderDateIssued": "data di pubblicazione", "LabelChapterName": "Chapter {0}", - "HeaderNewApiKey": "New Api Key", - "LabelAppName": "App name", + "HeaderNewApiKey": "Nessuna Chiave Api", + "LabelAppName": "Nome app", "LabelAppNameExample": "Example: Sickbeard, NzbDrone", - "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser.", + "HeaderNewApiKeyHelp": "Garantisci all'applicazione il permesso di comunicare con Media Browser.", "HeaderHttpHeaders": "Http Headers", "HeaderIdentificationHeader": "Identification Header", "LabelValue": "Value:", "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/kk.json b/MediaBrowser.Server.Implementations/Localization/Server/kk.json index caee463517..b588e6c11b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -199,6 +199,7 @@ "OptionFriday": "\u0436\u04b1\u043c\u0430", "OptionSaturday": "\u0441\u0435\u043d\u0431\u0456", "HeaderManagement": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a:", + "LabelManagement": "Management:", "OptionMissingImdbId": "IMDb Id \u0436\u043e\u049b", "OptionMissingTvdbId": "TheTVDB Id \u0436\u043e\u049b", "OptionMissingOverview": "\u0416\u0430\u043b\u043f\u044b \u0448\u043e\u043b\u0443 \u0436\u043e\u049b", @@ -626,10 +627,10 @@ "TabNowPlaying": "\u041e\u0439\u043d\u0430\u0442\u044b\u043b\u0443\u0434\u0430", "TabNavigation": "\u0428\u0430\u0440\u043b\u0430\u0443", "TabControls": "\u0411\u0430\u0441\u049b\u0430\u0440\u0443 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440\u0456", - "ButtonFullscreen": "\u0422\u043e\u043b\u044b\u049b \u044d\u043a\u0440\u0430\u043d", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "\u0421\u0430\u0445\u043d\u0430\u043b\u0430\u0440", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440", - "ButtonAudioTracks": "\u0414\u044b\u0431\u044b\u0441 \u0436\u043e\u043b\u0448\u044b\u049b\u0442\u0430\u0440\u044b", + "ButtonAudioTracks": "Audio tracks", "ButtonPreviousTrack": "\u0410\u043b\u0434\u044b\u04a3\u0493\u044b \u0436\u043e\u043b\u0448\u044b\u049b", "ButtonNextTrack": "\u041a\u0435\u043b\u0435\u0441\u0456 \u0436\u043e\u043b\u0448\u044b\u049b", "ButtonStop": "\u0422\u043e\u049b\u0442\u0430\u0442\u0443", @@ -874,5 +875,14 @@ "LabelMatchType": "\u0421\u04d9\u0439\u043a\u0435\u0441 \u0442\u04af\u0440\u0456:", "OptionEquals": "\u0422\u0435\u04a3", "OptionRegex": "\u04b0\u0434\u0430\u0439\u044b \u04e9\u0440\u043d\u0435\u043a", - "OptionSubstring": "\u0406\u0448\u043a\u0456 \u0436\u043e\u043b" + "OptionSubstring": "\u0406\u0448\u043a\u0456 \u0436\u043e\u043b", + "TabView": "\u041a\u04e9\u0440\u0456\u043d\u0456\u0441", + "TabSort": "\u0421\u04b1\u0440\u044b\u043f\u0442\u0430\u0443", + "TabFilter": "\u0421\u04af\u0437\u0443", + "ButtonView": "\u049a\u0430\u0440\u0430\u0443", + "LabelPageSize": "\u042d\u043a\u0440\u0430\u043d \u04e9\u043b\u0448\u0435\u043c\u0456:", + "LabelView": "\u041a\u04e9\u0440\u0456\u043d\u0456\u0441:", + "TabUsers": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ko.json b/MediaBrowser.Server.Implementations/Localization/Server/ko.json index 18cc943e16..e1a405972a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ko.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ko.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ms.json b/MediaBrowser.Server.Implementations/Localization/Server/ms.json index f90a350182..515cd70a2d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Sunday", "OptionMonday": "Monday", "OptionTuesday": "Tuesday", @@ -198,7 +198,8 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Missing IMDb Id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nb.json b/MediaBrowser.Server.Implementations/Localization/Server/nb.json index 94927eb12e..c2afde015a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Fortsetter", "OptionEnded": "Avsluttet", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "S\u00f8ndag", "OptionMonday": "Mandag", "OptionTuesday": "Tirsdag", @@ -198,7 +198,8 @@ "OptionThursday": "Torsdag", "OptionFriday": "Fredag", "OptionSaturday": "L\u00f8rdag", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Mangler IMDb id", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Mangler oversikt", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Select Directory", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json index 7f1545c2bb..4e01acfa1d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -39,7 +39,7 @@ "HeaderSetupLibrary": "Stel uw mediabibliotheek in", "ButtonAddMediaFolder": "Mediamap toevoegen", "LabelFolderType": "Maptype:", - "MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een plug-in vereist, bijvoorbeeld GameBrowser of MB Bookshelf.", + "MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een Invoegtoepassing vereist, bijvoorbeeld GameBrowser of MB Bookshelf.", "ReferToMediaLibraryWiki": "Raadpleeg de mediabibliotheek wiki.", "LabelCountry": "Land:", "LabelLanguage": "Taal:", @@ -152,9 +152,9 @@ "OptionResumable": "Hervatbaar", "ScheduledTasksHelp": "Klik op een taak om het schema aan te passen.", "ScheduledTasksTitle": "Geplande taken", - "TabMyPlugins": "Mijn Plug-ins", + "TabMyPlugins": "Mijn Invoegtoepassingen", "TabCatalog": "Catalogus", - "PluginsTitle": "Plug-ins", + "PluginsTitle": "Invoegtoepassingen", "HeaderAutomaticUpdates": "Automatische updates", "HeaderNowPlaying": "Wordt nu afgespeeld", "HeaderLatestAlbums": "Nieuwste Albums", @@ -199,6 +199,7 @@ "OptionFriday": "Vrijdag", "OptionSaturday": "Zaterdag", "HeaderManagement": "Beheer:", + "LabelManagement": "Management:", "OptionMissingImdbId": "IMDb Id ontbreekt", "OptionMissingTvdbId": "TheTVDB Id ontbreekt", "OptionMissingOverview": "Overzicht ontbreekt", @@ -284,7 +285,7 @@ "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Afbeelding opslag conventie:", "LabelImageSavingConventionHelp": "Media Browser herkent afbeeldingen van de meeste grote media-applicaties. Het kiezen van uw download conventie is handig als u ook gebruik wilt maken van andere producten.", - "OptionImageSavingCompatible": "Compatibel - Media Browser \/ Plex \/ XBMC", + "OptionImageSavingCompatible": "Compatibel - Media Browser \/ XBMC \/ Plex", "OptionImageSavingStandard": "Standaard - MB2", "ButtonSignIn": "Aanmelden", "TitleSignIn": "Aanmelden", @@ -332,10 +333,10 @@ "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", "LabelActiveService": "Actieve Service:", - "LabelActiveServiceHelp": "Er kunnen meerdere tv plug-ins worden ge\u00efnstalleerd, maar er kan er slechts een per keer actief zijn.", + "LabelActiveServiceHelp": "Er kunnen meerdere tv Invoegtoepassingen worden ge\u00efnstalleerd, maar er kan er slechts een per keer actief zijn.", "OptionAutomatic": "Automatisch", - "LiveTvPluginRequired": "Een Live TV service provider plug-in is vereist om door te gaan.", - "LiveTvPluginRequiredHelp": "Installeer a.u b een van onze beschikbare plug-ins, zoals Next PVR of ServerWmc.", + "LiveTvPluginRequired": "Een Live TV service provider Invoegtoepassing is vereist om door te gaan.", + "LiveTvPluginRequiredHelp": "Installeer a.u b een van onze beschikbare Invoegtoepassingen, zoals Next PVR of ServerWmc.", "LabelCustomizeOptionsPerMediaType": "Aanpassen voor mediatype", "OptionDownloadThumbImage": "Miniatuur", "OptionDownloadMenuImage": "Menu", @@ -576,9 +577,9 @@ "HeaderNotificationList": "Klik op een melding om de opties voor het versturen ervan te configureren .", "NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar", "NotificationOptionApplicationUpdateInstalled": "Programma-update ge\u00efnstalleerd", - "NotificationOptionPluginUpdateInstalled": "Plugin-update ge\u00efnstalleerd", - "NotificationOptionPluginInstalled": "Plugin ge\u00efnstalleerd", - "NotificationOptionPluginUninstalled": "Plugin verwijderd", + "NotificationOptionPluginUpdateInstalled": "Invoegtoepassings-update ge\u00efnstalleerd", + "NotificationOptionPluginInstalled": "Invoegtoepassing ge\u00efnstalleerd", + "NotificationOptionPluginUninstalled": "Invoegtoepassing verwijderd", "NotificationOptionVideoPlayback": "Video afspelen gestart", "NotificationOptionAudioPlayback": "Audio afspelen gestart", "NotificationOptionGamePlayback": "Game gestart", @@ -589,7 +590,7 @@ "NotificationOptionInstallationFailed": "Mislukken van de installatie", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionNewLibraryContentMultiple": "Nieuwe content toegevoegd (meerdere)", - "SendNotificationHelp": "Meldingen worden geplaatst in de inbox op het dashboard. Blader door de Plug-ins catalogus om aanvullende opties voor meldingen te installeren.", + "SendNotificationHelp": "Meldingen worden geplaatst in de inbox op het dashboard. Blader door de Invoegtoepassings catalogus om aanvullende opties voor meldingen te installeren.", "NotificationOptionServerRestartRequired": "Server herstart nodig", "LabelNotificationEnabled": "Deze melding inschakelen", "LabelMonitorUsers": "Monitor activiteit van:", @@ -599,10 +600,10 @@ "CategoryUser": "Gebruiker", "CategorySystem": "Systeem", "CategoryApplication": "Toepassing", - "CategoryPlugin": "Plug-in", + "CategoryPlugin": "Invoegtoepassing", "LabelMessageTitle": "Titel van het bericht:", "LabelAvailableTokens": "Beschikbaar tokens:", - "AdditionalNotificationServices": "Blader door de plug-in catalogus om aanvullende meldingsdiensten te installeren.", + "AdditionalNotificationServices": "Blader door de Invoegtoepassings catalogus om aanvullende meldingsdiensten te installeren.", "OptionAllUsers": "Alle gebruikers", "OptionAdminUsers": "Beheerders", "OptionCustomUsers": "Aangepast", @@ -626,17 +627,17 @@ "TabNowPlaying": "Wordt nu afgespeeld", "TabNavigation": "Navigatie", "TabControls": "Besturing", - "ButtonFullscreen": "Volledig scherm in-\/uitschakelen", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Scenes", "ButtonSubtitles": "Ondertitels", "ButtonAudioTracks": "Audio tracks", - "ButtonPreviousTrack": "Vorig nummer", - "ButtonNextTrack": "Volgend nummer", + "ButtonPreviousTrack": "Vorige track", + "ButtonNextTrack": "Volgende track", "ButtonStop": "Stop", "ButtonPause": "Pauze", "LabelGroupMoviesIntoCollections": "Groepeer films in verzamelingen", "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films die behoren tot een verzameling worden weergegeven als een gegroepeerd object.", - "NotificationOptionPluginError": "Plug-in fout", + "NotificationOptionPluginError": "Invoegtoepassings fout", "ButtonVolumeUp": "Volume omhoog", "ButtonVolumeDown": "Volume omlaag", "ButtonMute": "Dempen", @@ -722,7 +723,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Dit is vereist voor bepaalde apparaten die zo goed op tijd zoeken.", "HeaderSubtitleDownloadingHelp": "Bij het scannen van uw videobestanden kan Media Browser naar ontbrekende ondertiteling zoeken en deze downloaden bij ondertiteling providers zoals OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "Download ondertiteling voor:", - "MessageNoChapterProviders": "Installeer een hoofdstuk provider plug-in zoals ChapterDb of tagChimp om extra hoofdstuk opties in te schakelen.", + "MessageNoChapterProviders": "Installeer een hoofdstuk provider Invoegtoepassing zoals ChapterDb om extra hoofdstuk opties in te schakelen.", "LabelSkipIfGraphicalSubsPresent": "Overslaan als de video al grafische ondertitels bevat", "LabelSkipIfGraphicalSubsPresentHelp": "Tekstversies houden van ondertitels zal resulteren in meer effici\u00ebnte levering aan mobiele clients.", "TabSubtitles": "Ondertiteling", @@ -730,7 +731,7 @@ "HeaderDownloadChaptersFor": "Download hoofdstuk namen voor:", "LabelOpenSubtitlesUsername": "Gebruikersnaam Open Subtitles:", "LabelOpenSubtitlesPassword": "Wachtwoord Open Subtitles:", - "HeaderChapterDownloadingHelp": "Wanneer Media Browser uw videobestanden scant kan het gebruiksvriendelijke namen voor hoofdstukken downloaden van het internet met behulp van hoofdstuk plug-ins zoals ChapterDb en tagChimp.", + "HeaderChapterDownloadingHelp": "Wanneer Media Browser uw videobestanden scant kan het gebruiksvriendelijke namen voor hoofdstukken downloaden van het internet met behulp van hoofdstuk Invoegtoepassing zoals ChapterDb.", "LabelPlayDefaultAudioTrack": "Speel standaard audio spoor ongeacht taal", "LabelSubtitlePlaybackMode": "Ondertitelingsmode:", "LabelDownloadLanguages": "Download talen:", @@ -740,8 +741,8 @@ "HeaderSendMessage": "Stuur bericht", "ButtonSend": "Stuur", "LabelMessageText": "Bericht tekst:", - "MessageNoAvailablePlugins": "Geen beschikbare plugins.", - "LabelDisplayPluginsFor": "Toon plugins voor:", + "MessageNoAvailablePlugins": "Geen beschikbare Invoegtoepassingen.", + "LabelDisplayPluginsFor": "Toon Invoegtoepassingen voor:", "PluginTabMediaBrowserClassic": "MB Classic", "PluginTabMediaBrowserTheater": "MB Theater", "LabelEpisodeName": "Naam aflevering", @@ -802,7 +803,7 @@ "LabelChannelDownloadPathHelp": "Geef een eigen download pad op als dit gewenst is, leeglaten voor dowloaden naar de interne program data map.", "LabelChannelDownloadAge": "Verwijder inhoud na: (dagen)", "LabelChannelDownloadAgeHelp": "Gedownloade inhoud die ouder is zal worden verwijderd. Afspelen via internet streaming blijft mogelijk.", - "ChannelSettingsFormHelp": "Installeer kanalen zoals Trailers en Vimeo in de plug-in catalogus.", + "ChannelSettingsFormHelp": "Installeer kanalen zoals Trailers en Vimeo in de Invoegtoepassings catalogus.", "LabelSelectCollection": "Selecteer verzameling:", "ViewTypeMovies": "Films", "ViewTypeTvShows": "TV", @@ -844,7 +845,7 @@ "HeaderBrandingHelp": "Pas het uiterlijk van Media Browser aan, aan de behoeften van uw groep of organisatie.", "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Dit wordt onderaan de login pagina weergegeven.", - "LabelAutomaticallyDonate": "Doneer dit bedrag automatisch per maand", + "LabelAutomaticallyDonate": "Doneer dit bedrag automatisch elke zes maanden", "LabelAutomaticallyDonateHelp": "U kunt dit via uw PayPal account op elk moment annuleren.", "OptionList": "Lijst", "TabDashboard": "Dashboard", @@ -874,5 +875,14 @@ "LabelMatchType": "Type overeenkomst:", "OptionEquals": "Is gelijk aan", "OptionRegex": "Regex", - "OptionSubstring": "Subtekenreeks" + "OptionSubstring": "Subtekenreeks", + "TabView": "Weergave", + "TabSort": "Sorteren", + "TabFilter": "Filter", + "ButtonView": "Weergave", + "LabelPageSize": "Schermgrootte:", + "LabelView": "Weergave:", + "TabUsers": "Gebruikers", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pl.json b/MediaBrowser.Server.Implementations/Localization/Server/pl.json index 43444b6890..b5b610654d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Niedziela", "OptionMonday": "Poniedzia\u0142ek", "OptionTuesday": "Wtorek", @@ -198,7 +198,8 @@ "OptionThursday": "Czwartek", "OptionFriday": "Pi\u0105tek", "OptionSaturday": "Sobota", - "HeaderManagement": "Management:", + "HeaderManagement": "Management", + "LabelManagement": "Management:", "OptionMissingImdbId": "Brakuje Id IMDb", "OptionMissingTvdbId": "Brakuje Id TheTVDB", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "Wybierz folder", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json index e60c7b3c3c..b635d73184 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -199,6 +199,7 @@ "OptionFriday": "Sexta-feira", "OptionSaturday": "S\u00e1bado", "HeaderManagement": "Gerenciamento:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Faltando Id IMDb", "OptionMissingTvdbId": "Faltando Id TheTVDB", "OptionMissingOverview": "Faltando Sinopse", @@ -626,12 +627,12 @@ "TabNowPlaying": "Reproduzindo Agora", "TabNavigation": "Navega\u00e7\u00e3o", "TabControls": "Controles", - "ButtonFullscreen": "Alternar tela cheia", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Cenas", "ButtonSubtitles": "Legendas", - "ButtonAudioTracks": "Faixas de \u00e1udio", - "ButtonPreviousTrack": "Faixa Anterior", - "ButtonNextTrack": "Pr\u00f3xima Faixa", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Faixa anterior", + "ButtonNextTrack": "Pr\u00f3xima faixa", "ButtonStop": "Parar", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Agrupar filmes nas cole\u00e7\u00f5es", @@ -874,5 +875,14 @@ "LabelMatchType": "Tipo de correspond\u00eancia", "OptionEquals": "Igual", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "Visualizar", + "TabSort": "Ordenar", + "TabFilter": "Filtro", + "ButtonView": "Visualizar", + "LabelPageSize": "Tamanho de exibi\u00e7\u00e3o:", + "LabelView": "Visualizar:", + "TabUsers": "Usu\u00e1rios", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json index c53ecd9d8e..12850f803d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -199,6 +199,7 @@ "OptionFriday": "Sexta", "OptionSaturday": "S\u00e1bado", "HeaderManagement": "Gest\u00e3o:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Id do IMDb em falta", "OptionMissingTvdbId": "iD do TheTVDB em falta", "OptionMissingOverview": "Descri\u00e7\u00e3o em falta", @@ -479,10 +480,10 @@ "HeaderProgram": "Programa", "HeaderClients": "Clientes", "LabelCompleted": "Terminado", - "LabelFailed": "Falhou", + "LabelFailed": "Failed", "LabelSkipped": "Ignorado", "HeaderEpisodeOrganization": "Organiza\u00e7\u00e3o dos Epis\u00f3dios", - "LabelSeries": "S\u00e9rie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "N\u00famero da temporada", "LabelEpisodeNumber": "N\u00famero do epis\u00f3dio", "LabelEndingEpisodeNumber": "N\u00famero do epis\u00f3dio final", @@ -629,9 +630,9 @@ "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Cenas", "ButtonSubtitles": "Legendas", - "ButtonAudioTracks": "Faixas de \u00e1udio", - "ButtonPreviousTrack": "Faixa Anterior", - "ButtonNextTrack": "Pr\u00f3xima Faixa", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Parar", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Group movies into collections", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json index 7d9e83cbc7..fcf3756ab0 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -19,13 +19,13 @@ "ThisWizardWillGuideYou": "\u042d\u0442\u043e\u0442 \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043f\u043e \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 \u0432\u0441\u0435 \u0444\u0430\u0437\u044b \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u044f\u0437\u044b\u043a.", "TellUsAboutYourself": "\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0438\u0442\u0435 \u043e \u0441\u0435\u0431\u0435", "LabelYourFirstName": "\u0412\u0430\u0448\u0435 \u0438\u043c\u044f:", - "MoreUsersCanBeAddedLater": "\u041f\u043e\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u041f\u0430\u043d\u0435\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430.", + "MoreUsersCanBeAddedLater": "\u041f\u043e\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c.", "UserProfilesIntro": "\u0412 Media Browser \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0438\u0437 \u043d\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u043c\u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c.", "LabelWindowsService": "\u0421\u043b\u0443\u0436\u0431\u0430 Windows", "AWindowsServiceHasBeenInstalled": "\u0421\u043b\u0443\u0436\u0431\u0430 Windows \u0431\u044b\u043b\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430.", "WindowsServiceIntro1": "\u041e\u0431\u044b\u0447\u043d\u043e Media Browser Server \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e \u0437\u043d\u0430\u0447\u043a\u043e\u043c \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u043c \u043b\u043e\u0442\u043a\u0435, \u043d\u043e \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431\u0430\u043c\u0438 Windows.", "WindowsServiceIntro2": "\u041a\u043e\u0433\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u043b\u0443\u0436\u0431\u0430 Windows, \u043f\u0440\u0438\u043c\u0438\u0442\u0435 \u043a \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044e, \u0447\u0442\u043e \u043e\u043d\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441\u043e \u0437\u043d\u0430\u0447\u043a\u043e\u043c \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u043c \u043b\u043e\u0442\u043a\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u0437\u043d\u0430\u0447\u043e\u043a \u0432 \u043b\u043e\u0442\u043a\u0435, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u043b\u0443\u0436\u0431\u0443. \u0421\u043b\u0443\u0436\u0431\u0443 \u0442\u0430\u043a\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0443\u0436\u043d\u043e \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043e\u0431\u043b\u0430\u0434\u0430\u044f \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u043f\u0440\u0430\u0432\u0430\u043c\u0438, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0441\u043b\u0443\u0436\u0431\u0435 \u043d\u0435 \u0443\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u0430\u043c\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f, \u0442\u0430\u043a \u0447\u0442\u043e \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u043e\u0432\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0430\u0448\u0435 \u0432\u043c\u0435\u0448\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e.", - "WizardCompleted": "\u042d\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442. Media Browser \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0441\u0431\u043e\u0440 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0438\u0437 \u043d\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0438 \u0442\u043e\u0433\u0434\u0430 \u0449\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c<\/b>, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u0432 \u041f\u0430\u043d\u0435\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430<\/b>.", + "WizardCompleted": "\u042d\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442. Media Browser \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0441\u0431\u043e\u0440 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0438\u0437 \u043d\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0438 \u0442\u043e\u0433\u0434\u0430 \u0449\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c<\/b>, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u0432 \u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c<\/b>.", "LabelConfigureSettings": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432", "LabelEnableVideoImageExtraction": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0430 \u0438\u0437 \u0432\u0438\u0434\u0435\u043e", "VideoImageExtractionHelp": "\u0414\u043b\u044f \u0432\u0438\u0434\u0435\u043e, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0449\u0451 \u200b\u200b\u043d\u0435 \u0438\u043c\u0435\u044e\u0442 \u043e\u0431\u043b\u043e\u0436\u043a\u0438, \u0438 \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0442\u0430\u043a\u043e\u0432\u044b\u0445. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438 \u043f\u0440\u043e\u0434\u043b\u0438\u0442\u0441\u044f \u0435\u0449\u0451 \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f, \u043d\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u0441\u0442\u0430\u043d\u0435\u0442 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0438\u044f\u0442\u043d\u043e\u0435 \u0434\u043b\u044f \u0433\u043b\u0430\u0437 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445.", @@ -199,6 +199,7 @@ "OptionFriday": "\u043f\u044f\u0442\u043d\u0438\u0446\u0430", "OptionSaturday": "\u0441\u0443\u0431\u0431\u043e\u0442\u0430", "HeaderManagement": "\u0414\u043b\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445:", + "LabelManagement": "Management:", "OptionMissingImdbId": "\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d IMDb Id", "OptionMissingTvdbId": "\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d TheTVDB Id", "OptionMissingOverview": "\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e \u043e\u0431\u043e\u0437\u0440\u0435\u043d\u0438\u0435", @@ -437,9 +438,9 @@ "HeaderSystemDlnaProfiles": "\u0421\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438", "CustomDlnaProfilesHelp": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c.", "SystemDlnaProfilesHelp": "\u0421\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u043c \u043f\u0440\u043e\u0444\u0438\u043b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b \u0432 \u043d\u043e\u0432\u043e\u043c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u043c \u043f\u0440\u043e\u0444\u0438\u043b\u0435.", - "TitleDashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430", + "TitleDashboard": "\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c", "TabHome": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f", - "TabInfo": "\u0418\u043d\u0444\u043e", + "TabInfo": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f", "HeaderLinks": "\u0421\u0441\u044b\u043b\u043a\u0438", "HeaderSystemPaths": "\u0421\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043f\u0443\u0442\u0438", "LinkCommunity": "\u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e", @@ -479,7 +480,7 @@ "HeaderProgram": "\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430", "HeaderClients": "\u041a\u043b\u0438\u0435\u043d\u0442\u044b", "LabelCompleted": "\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e", - "LabelFailed": "\u041d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e", + "LabelFailed": "\u041d\u0435\u0443\u0434\u0430\u0447\u0430", "LabelSkipped": "\u041e\u0442\u043b\u043e\u0436\u0435\u043d\u043e", "HeaderEpisodeOrganization": "\u0420\u0435\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u044d\u043f\u0438\u0437\u043e\u0434\u0430", "LabelSeries": "\u0421\u0435\u0440\u0438\u0430\u043b:", @@ -589,7 +590,7 @@ "NotificationOptionInstallationFailed": "\u0421\u0431\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438", "NotificationOptionNewLibraryContent": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e", "NotificationOptionNewLibraryContentMultiple": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e (\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e)", - "SendNotificationHelp": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u044f\u0449\u0438\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043f\u0430\u043d\u0435\u043b\u0438 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430. \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.", + "SendNotificationHelp": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u044f\u0449\u0438\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u0438. \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.", "NotificationOptionServerRestartRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430", "LabelNotificationEnabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u043e\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435", "LabelMonitorUsers": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043e\u0442:", @@ -626,10 +627,10 @@ "TabNowPlaying": "\u041f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f", "TabNavigation": "\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f", "TabControls": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f", - "ButtonFullscreen": "\u041f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "\u0421\u0446\u0435\u043d\u044b", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b", - "ButtonAudioTracks": "\u0410\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0438", + "ButtonAudioTracks": "Audio tracks", "ButtonPreviousTrack": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "ButtonNextTrack": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", @@ -673,7 +674,7 @@ "TabContainers": "\u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b", "TabCodecs": "\u041a\u043e\u0434\u0435\u043a\u0438", "TabResponses": "\u041e\u0442\u043a\u043b\u0438\u043a\u0438", - "HeaderProfileInformation": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u0440\u043e\u0444\u0438\u043b\u0435", + "HeaderProfileInformation": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e \u043f\u0440\u043e\u0444\u0438\u043b\u0435", "LabelEmbedAlbumArtDidl": "\u0412\u043d\u0435\u0434\u0440\u044f\u0442\u044c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0430\u043b\u044c\u0431\u043e\u043c\u0430 \u0432 DIDL", "LabelEmbedAlbumArtDidlHelp": "\u0414\u043b\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0439 \u0430\u043b\u044c\u0431\u043e\u043c\u043e\u0432 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c. \u0414\u043b\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0436\u0435, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430, \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u043d\u0435 \u0443\u0434\u0430\u0441\u0442\u0441\u044f.", "LabelAlbumArtPN": "PN \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0430\u043b\u044c\u0431\u043e\u043c\u0430", @@ -847,7 +848,7 @@ "LabelAutomaticallyDonate": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u0430\u0440\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u043a\u0430\u0436\u0434\u044b\u0435 \u0448\u0435\u0441\u0442\u044c \u043c\u0435\u0441\u044f\u0446\u0435\u0432", "LabelAutomaticallyDonateHelp": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c PayPal.", "OptionList": "\u0421\u043f\u0438\u0441\u043e\u043a", - "TabDashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430", + "TabDashboard": "\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c", "TitleServer": "\u0421\u0435\u0440\u0432\u0435\u0440", "LabelCache": "Cache:", "LabelLogs": "Logs:", @@ -867,12 +868,21 @@ "HeaderNewApiKey": "\u041d\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 API", "LabelAppName": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f", "LabelAppNameExample": "\u041f\u0440\u0438\u043c\u0435\u0440: Sickbeard, NzbDrone", - "HeaderNewApiKeyHelp": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Media Browser.", + "HeaderNewApiKeyHelp": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a Media Browser.", "HeaderHttpHeaders": "HTTP-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438", "HeaderIdentificationHeader": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", "LabelValue": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435:", "LabelMatchType": "\u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0442\u0438\u043f:", "OptionEquals": "\u0420\u0430\u0432\u043d\u044f\u0435\u0442\u0441\u044f", "OptionRegex": "\u0420\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435", - "OptionSubstring": "\u041f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0430" + "OptionSubstring": "\u041f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0430", + "TabView": "\u0412\u0438\u0434", + "TabSort": "\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430", + "TabFilter": "\u0424\u0438\u043b\u044c\u0442\u0440", + "ButtonView": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c", + "LabelPageSize": "\u0420\u0430\u0437\u043c\u0435\u0440 \u044d\u043a\u0440\u0430\u043d\u0430:", + "LabelView": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440:", + "TabUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ 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 741bc52ca0..4a23b0c073 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -1,893 +1,904 @@ { "LabelExit": "Exit", - "LabelVisitCommunity": "Visit Community", - "LabelGithubWiki": "Github Wiki", - "LabelSwagger": "Swagger", - "LabelStandard": "Standard", - "LabelViewApiDocumentation": "View Api Documentation", - "LabelBrowseLibrary": "Browse Library", - "LabelConfigureMediaBrowser": "Configure Media Browser", - "LabelOpenLibraryViewer": "Open Library Viewer", - "LabelRestartServer": "Restart Server", - "LabelShowLogWindow": "Show Log Window", - "LabelPrevious": "Previous", - "LabelFinish": "Finish", - "LabelNext": "Next", - "LabelYoureDone": "You're Done!", - "WelcomeToMediaBrowser": "Welcome to Media Browser!", - "TitleMediaBrowser": "Media Browser", - "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.", - "TellUsAboutYourself": "Tell us about yourself", - "LabelYourFirstName": "Your first name:", - "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", - "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", - "LabelWindowsService": "Windows Service", - "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", - "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", - "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", - "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click Finish to view the Dashboard.", - "LabelConfigureSettings": "Configure settings", - "LabelEnableVideoImageExtraction": "Enable video image extraction", - "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", - "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", - "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", - "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", - "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", - "ButtonOk": "Ok", - "ButtonCancel": "Cancel", - "ButtonNew": "New", - "HeaderSetupLibrary": "Setup your media library", - "ButtonAddMediaFolder": "Add media folder", - "LabelFolderType": "Folder type:", - "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", - "ReferToMediaLibraryWiki": "Refer to the media library wiki.", - "LabelCountry": "Country:", - "LabelLanguage": "Language:", - "HeaderPreferredMetadataLanguage": "Preferred metadata language:", - "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", - "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", - "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", - "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations.", - "TabPreferences": "Preferences", - "TabPassword": "Password", - "TabLibraryAccess": "Library Access", - "TabImage": "Image", - "TabProfile": "Profile", - "TabMetadata": "Metadata", - "TabImages": "Images", - "TabNotifications": "Notifications", - "TabCollectionTitles": "Titles", - "LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", - "LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons", - "HeaderVideoPlaybackSettings": "Video Playback Settings", - "HeaderPlaybackSettings": "Playback Settings", - "LabelAudioLanguagePreference": "Audio language preference:", - "LabelSubtitleLanguagePreference": "Subtitle language preference:", - "OptionDefaultSubtitles": "Default", - "OptionOnlyForcedSubtitles": "Only forced subtitles", - "OptionAlwaysPlaySubtitles": "Always play subtitles", - "OptionNoSubtitles": "None", - "OptionDefaultSubtitlesHelp": "Subtitles matching the language preference will be loaded when the audio is in a foreign language.", - "OptionOnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.", - "OptionAlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", - "OptionNoSubtitlesHelp": "Subtitles will not be loaded by default.", - "TabProfiles": "Profiles", - "TabSecurity": "Security", - "ButtonAddUser": "Add User", - "ButtonSave": "Save", - "ButtonResetPassword": "Reset Password", - "LabelNewPassword": "New password:", - "LabelNewPasswordConfirm": "New password confirm:", - "HeaderCreatePassword": "Create Password", - "LabelCurrentPassword": "Current password:", - "LabelMaxParentalRating": "Maximum allowed parental rating:", - "MaxParentalRatingHelp": "Content with a higher rating will be hidden from this user.", - "LibraryAccessHelp": "Select the media folders to share with this user. Administrators will be able to edit all folders using the metadata manager.", - "ChannelAccessHelp": "Select the channels to share with this user. Administrators will be able to edit all channels using the metadata manager.", - "ButtonDeleteImage": "Delete Image", - "LabelSelectUsers": "Select users:", - "ButtonUpload": "Upload", - "HeaderUploadNewImage": "Upload New Image", - "LabelDropImageHere": "Drop Image Here", - "ImageUploadAspectRatioHelp": "1:1 Aspect Ratio Recommended. JPG/PNG only.", - "MessageNothingHere": "Nothing here.", - "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", - "TabSuggested": "Suggested", - "TabLatest": "Latest", - "TabUpcoming": "Upcoming", - "TabShows": "Shows", - "TabEpisodes": "Episodes", - "TabGenres": "Genres", - "TabPeople": "People", - "TabNetworks": "Networks", - "HeaderUsers": "Users", - "HeaderFilters": "Filters:", - "ButtonFilter": "Filter", - "OptionFavorite": "Favorites", - "OptionLikes": "Likes", - "OptionDislikes": "Dislikes", - "OptionActors": "Actors", - "OptionGuestStars": "Guest Stars", - "OptionDirectors": "Directors", - "OptionWriters": "Writers", - "OptionProducers": "Producers", - "HeaderResume": "Resume", - "HeaderNextUp": "Next Up", - "NoNextUpItemsMessage": "None found. Start watching your shows!", - "HeaderLatestEpisodes": "Latest Episodes", - "HeaderPersonTypes": "Person Types:", - "TabSongs": "Songs", - "TabAlbums": "Albums", - "TabArtists": "Artists", - "TabAlbumArtists": "Album Artists", - "TabMusicVideos": "Music Videos", - "ButtonSort": "Sort", - "HeaderSortBy": "Sort By:", - "HeaderSortOrder": "Sort Order:", - "OptionPlayed": "Played", - "OptionUnplayed": "Unplayed", - "OptionAscending": "Ascending", - "OptionDescending": "Descending", - "OptionRuntime": "Runtime", - "OptionReleaseDate": "Release Date", - "OptionPlayCount": "Play Count", - "OptionDatePlayed": "Date Played", - "OptionDateAdded": "Date Added", - "OptionAlbumArtist": "Album Artist", - "OptionArtist": "Artist", - "OptionAlbum": "Album", - "OptionTrackName": "Track Name", - "OptionCommunityRating": "Community Rating", - "OptionNameSort": "Name", - "OptionFolderSort": "Folders", - "OptionBudget": "Budget", - "OptionRevenue": "Revenue", - "OptionPoster": "Poster", - "OptionBackdrop": "Backdrop", - "OptionTimeline": "Timeline", - "OptionThumb": "Thumb", - "OptionBanner": "Banner", - "OptionCriticRating": "Critic Rating", - "OptionVideoBitrate": "Video Bitrate", - "OptionResumable": "Resumable", - "ScheduledTasksHelp": "Click a task to adjust its schedule.", - "ScheduledTasksTitle": "Scheduled Tasks", - "TabMyPlugins": "My Plugins", - "TabCatalog": "Catalog", - "PluginsTitle": "Plugins", - "HeaderAutomaticUpdates": "Automatic Updates", - "HeaderNowPlaying": "Now Playing", - "HeaderLatestAlbums": "Latest Albums", - "HeaderLatestSongs": "Latest Songs", - "HeaderRecentlyPlayed": "Recently Played", - "HeaderFrequentlyPlayed": "Frequently Played", - "DevBuildWarning": "Dev builds are the bleeding edge. Released often, these build have not been tested. The application may crash and entire features may not work at all.", - "LabelVideoType": "Video Type:", - "OptionBluray": "Bluray", - "OptionDvd": "Dvd", - "OptionIso": "Iso", - "Option3D": "3D", - "LabelFeatures": "Features:", - "LabelService": "Service:", - "LabelStatus": "Status:", - "LabelVersion": "Version:", - "LabelLastResult": "Last result:", - "OptionHasSubtitles": "Subtitles", - "OptionHasTrailer": "Trailer", - "OptionHasThemeSong": "Theme Song", - "OptionHasThemeVideo": "Theme Video", - "TabMovies": "Movies", - "TabStudios": "Studios", - "TabTrailers": "Trailers", - "HeaderLatestMovies": "Latest Movies", - "HeaderLatestTrailers": "Latest Trailers", - "OptionHasSpecialFeatures": "Special Features", - "OptionImdbRating": "IMDb Rating", - "OptionParentalRating": "Parental Rating", - "OptionPremiereDate": "Premiere Date", - "TabBasic": "Basic", - "TabAdvanced": "Advanced", - "HeaderStatus": "Status", - "OptionContinuing": "Continuing", - "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", - "OptionSunday": "Sunday", - "OptionMonday": "Monday", - "OptionTuesday": "Tuesday", - "OptionWednesday": "Wednesday", - "OptionThursday": "Thursday", - "OptionFriday": "Friday", - "OptionSaturday": "Saturday", - "HeaderManagement": "Management:", - "OptionMissingImdbId": "Missing IMDb Id", - "OptionMissingTvdbId": "Missing TheTVDB Id", - "OptionMissingOverview": "Missing Overview", - "OptionFileMetadataYearMismatch": "File/Metadata Years Mismatched", - "TabGeneral": "General", - "TitleSupport": "Support", - "TabLog": "Log", - "TabAbout": "About", - "TabSupporterKey": "Supporter Key", - "TabBecomeSupporter": "Become a Supporter", - "MediaBrowserHasCommunity": "Media Browser has a thriving community of users and contributors.", - "CheckoutKnowledgeBase": "Check out our knowledge base to help you get the most out of Media Browser.", - "SearchKnowledgeBase": "Search the Knowledge Base", - "VisitTheCommunity": "Visit the Community", - "VisitMediaBrowserWebsite": "Visit the Media Browser Web Site", - "VisitMediaBrowserWebsiteLong": "Visit the Media Browser Web site to catch the latest news and keep up with the developer blog.", - "OptionHideUser": "Hide this user from login screens", - "OptionDisableUser": "Disable this user", - "OptionDisableUserHelp": "If disabled the server will not allow any connections from this user. Existing connections will be abruptly terminated.", - "HeaderAdvancedControl": "Advanced Control", - "LabelName": "Name:", - "OptionAllowUserToManageServer": "Allow this user to manage the server", - "HeaderFeatureAccess": "Feature Access", - "OptionAllowMediaPlayback": "Allow media playback", - "OptionAllowBrowsingLiveTv": "Allow browsing of live tv", - "OptionAllowDeleteLibraryContent": "Allow this user to delete library content", - "OptionAllowManageLiveTv": "Allow management of live tv recordings", - "OptionAllowRemoteControlOthers": "Allow this user to remote control other users", - "OptionMissingTmdbId": "Missing Tmdb Id", - "OptionIsHD": "HD", - "OptionIsSD": "SD", - "OptionMetascore": "Metascore", - "ButtonSelect": "Select", - "ButtonSearch": "Search", - "ButtonGroupVersions": "Group Versions", - "ButtonAddToCollection": "Add to Collection", - "PismoMessage": "Utilizing Pismo File Mount through a donated license.", - "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java/C# converters through a donated license.", - "HeaderCredits": "Credits", - "PleaseSupportOtherProduces": "Please support other free products we utilize:", - "VersionNumber": "Version {0}", - "TabPaths": "Paths", - "TabServer": "Server", - "TabTranscoding": "Transcoding", - "TitleAdvanced": "Advanced", - "LabelAutomaticUpdateLevel": "Automatic update level", - "OptionRelease": "Official Release", - "OptionBeta": "Beta", - "OptionDev": "Dev (Unstable)", - "LabelAllowServerAutoRestart": "Allow the server to restart automatically to apply updates", - "LabelAllowServerAutoRestartHelp": "The server will only restart during idle periods, when no users are active.", - "LabelEnableDebugLogging": "Enable debug logging", - "LabelRunServerAtStartup": "Run server at startup", - "LabelRunServerAtStartupHelp": "This will start the tray icon on windows startup. To start the windows service, uncheck this and run the service from the windows control panel. Please note that you cannot run both at the same time, so you will need to exit the tray icon before starting the service.", - "ButtonSelectDirectory": "Select Directory", - "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", - "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", - "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", - "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", - "LabelTranscodingTempPath": "Transcoding temporary path:", - "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", - "TabBasics": "Basics", - "TabTV": "TV", - "TabGames": "Games", - "TabMusic": "Music", - "TabOthers": "Others", - "HeaderExtractChapterImagesFor": "Extract chapter images for:", - "OptionMovies": "Movies", - "OptionEpisodes": "Episodes", - "OptionOtherVideos": "Other Videos", - "TitleMetadata": "Metadata", - "LabelAutomaticUpdatesFanart": "Enable automatic updates from FanArt.tv", - "LabelAutomaticUpdatesTmdb": "Enable automatic updates from TheMovieDB.org", - "LabelAutomaticUpdatesTvdb": "Enable automatic updates from TheTVDB.com", - "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", - "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", - "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", - "LabelMetadataDownloadLanguage": "Preferred download language:", - "ButtonAutoScroll": "Auto-scroll", - "LabelImageSavingConvention": "Image saving convention:", - "LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", - "OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex", - "OptionImageSavingStandard": "Standard - MB2", - "ButtonSignIn": "Sign In", - "TitleSignIn": "Sign In", - "HeaderPleaseSignIn": "Please sign in", - "LabelUser": "User:", - "LabelPassword": "Password:", - "ButtonManualLogin": "Manual Login", - "PasswordLocalhostMessage": "Passwords are not required when logging in from localhost.", - "TabGuide": "Guide", - "TabChannels": "Channels", - "TabCollections": "Collections", - "HeaderChannels": "Channels", - "TabRecordings": "Recordings", - "TabScheduled": "Scheduled", - "TabSeries": "Series", - "TabFavorites": "Favorites", - "TabMyLibrary": "My Library", - "ButtonCancelRecording": "Cancel Recording", - "HeaderPrePostPadding": "Pre/Post Padding", - "LabelPrePaddingMinutes": "Pre-padding minutes:", - "OptionPrePaddingRequired": "Pre-padding is required in order to record.", - "LabelPostPaddingMinutes": "Post-padding minutes:", - "OptionPostPaddingRequired": "Post-padding is required in order to record.", - "HeaderWhatsOnTV": "What's On", - "HeaderUpcomingTV": "Upcoming TV", - "TabStatus": "Status", - "TabSettings": "Settings", - "ButtonRefreshGuideData": "Refresh Guide Data", - "OptionPriority": "Priority", - "OptionRecordOnAllChannels": "Record program on all channels", - "OptionRecordAnytime": "Record program at any time", - "OptionRecordOnlyNewEpisodes": "Record only new episodes", - "HeaderDays": "Days", - "HeaderActiveRecordings": "Active Recordings", - "HeaderLatestRecordings": "Latest Recordings", - "HeaderAllRecordings": "All Recordings", - "ButtonPlay": "Play", - "ButtonEdit": "Edit", - "ButtonRecord": "Record", - "ButtonDelete": "Delete", - "ButtonRemove": "Remove", - "OptionRecordSeries": "Record Series", - "HeaderDetails": "Details", - "ButtonCancelRecording": "Cancel Recording", - "TitleLiveTV": "Live TV", - "LabelNumberOfGuideDays": "Number of days of guide data to download:", - "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", - "LabelActiveService": "Active Service:", - "LabelActiveServiceHelp": "Multiple tv plugins can be installed but only one can be active at a time.", - "OptionAutomatic": "Auto", - "LiveTvPluginRequired": "A Live TV service provider plugin is required in order to continue.", - "LiveTvPluginRequiredHelp": "Please install one of our available plugins, such as Next Pvr or ServerWmc.", - "LabelCustomizeOptionsPerMediaType": "Customize for media type:", - "OptionDownloadThumbImage": "Thumb", - "OptionDownloadMenuImage": "Menu", - "OptionDownloadLogoImage": "Logo", - "OptionDownloadBoxImage": "Box", - "OptionDownloadDiscImage": "Disc", - "OptionDownloadBannerImage": "Banner", - "OptionDownloadBackImage": "Back", - "OptionDownloadArtImage": "Art", - "OptionDownloadPrimaryImage": "Primary", - "HeaderFetchImages": "Fetch Images:", - "HeaderImageSettings": "Image Settings", - "TabOther": "Other", - "LabelMaxBackdropsPerItem": "Maximum number of backdrops per item:", - "LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:", - "LabelMinBackdropDownloadWidth": "Minimum backdrop download width:", - "LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:", - "ButtonAddScheduledTaskTrigger": "Add Task Trigger", - "HeaderAddScheduledTaskTrigger": "Add Task Trigger", - "ButtonAdd": "Add", - "LabelTriggerType": "Trigger Type:", - "OptionDaily": "Daily", - "OptionWeekly": "Weekly", - "OptionOnInterval": "On an interval", - "OptionOnAppStartup": "On application startup", - "OptionAfterSystemEvent": "After a system event", - "LabelDay": "Day:", - "LabelTime": "Time:", - "LabelEvent": "Event:", - "OptionWakeFromSleep": "Wake from sleep", - "LabelEveryXMinutes": "Every:", - "HeaderTvTuners": "Tuners", - "HeaderGallery": "Gallery", - "HeaderLatestGames": "Latest Games", - "HeaderRecentlyPlayedGames": "Recently Played Games", - "TabGameSystems": "Game Systems", - "TitleMediaLibrary": "Media Library", - "TabFolders": "Folders", - "TabPathSubstitution": "Path Substitution", - "LabelSeasonZeroDisplayName": "Season 0 display name:", - "LabelEnableRealtimeMonitor": "Enable real time monitoring", - "LabelEnableRealtimeMonitorHelp": "Changes will be processed immediately, on supported file systems.", - "ButtonScanLibrary": "Scan Library", - "HeaderNumberOfPlayers": "Players:", - "OptionAnyNumberOfPlayers": "Any", - "Option1Player": "1+", - "Option2Player": "2+", - "Option3Player": "3+", - "Option4Player": "4+", - "HeaderMediaFolders": "Media Folders", - "HeaderThemeVideos": "Theme Videos", - "HeaderThemeSongs": "Theme Songs", - "HeaderScenes": "Scenes", - "HeaderAwardsAndReviews": "Awards and Reviews", - "HeaderSoundtracks": "Soundtracks", - "HeaderMusicVideos": "Music Videos", - "HeaderSpecialFeatures": "Special Features", - "HeaderCastCrew": "Cast & Crew", - "HeaderAdditionalParts": "Additional Parts", - "ButtonSplitVersionsApart": "Split Versions Apart", - "ButtonPlayTrailer": "Trailer", - "LabelMissing": "Missing", - "LabelOffline": "Offline", - "PathSubstitutionHelp": "Path substitutions are used for mapping a path on the server to a path that clients are able to access. By allowing clients direct access to media on the server they may be able to play them directly over the network and avoid using server resources to stream and transcode them.", - "HeaderFrom": "From", - "HeaderTo": "To", - "LabelFrom": "From:", - "LabelFromHelp": "Example: D:\\Movies (on the server)", - "LabelTo": "To:", - "LabelToHelp": "Example: \\\\MyServer\\Movies (a path clients can access)", - "ButtonAddPathSubstitution": "Add Substitution", - "OptionSpecialEpisode": "Specials", - "OptionMissingEpisode": "Missing Episodes", - "OptionUnairedEpisode": "Unaired Episodes", - "OptionEpisodeSortName": "Episode Sort Name", - "OptionSeriesSortName": "Series Name", - "OptionTvdbRating": "Tvdb Rating", - "HeaderTranscodingQualityPreference": "Transcoding Quality Preference:", - "OptionAutomaticTranscodingHelp": "The server will decide quality and speed", - "OptionHighSpeedTranscodingHelp": "Lower quality, but faster encoding", - "OptionHighQualityTranscodingHelp": "Higher quality, but slower encoding", - "OptionMaxQualityTranscodingHelp": "Best quality with slower encoding and high CPU usage", - "OptionHighSpeedTranscoding": "Higher speed", - "OptionHighQualityTranscoding": "Higher quality", - "OptionMaxQualityTranscoding": "Max quality", - "OptionEnableDebugTranscodingLogging": "Enable debug transcoding logging", - "OptionEnableDebugTranscodingLoggingHelp": "This will create very large log files and is only recommended as needed for troubleshooting purposes.", - "OptionEnableDebugTranscodingLogging": "Enable debug transcoding logging", - "OptionUpscaling": "Allow clients to request upscaled video", - "OptionUpscalingHelp": "In some cases this will result in improved video quality but will increase CPU usage.", - "EditCollectionItemsHelp": "Add or remove any movies, series, albums, books or games you wish to group within this collection.", - "HeaderAddTitles": "Add Titles", - "LabelEnableDlnaPlayTo": "Enable DLNA Play To", - "LabelEnableDlnaPlayToHelp": "Media Browser can detect devices within your network and offer the ability to remote control them.", - "LabelEnableDlnaDebugLogging": "Enable DLNA debug logging", - "LabelEnableDlnaDebugLoggingHelp": "This will create large log files and should only be used as needed for troubleshooting purposes.", - "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval (seconds)", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Media Browser.", - "HeaderCustomDlnaProfiles": "Custom Profiles", - "HeaderSystemDlnaProfiles": "System Profiles", - "CustomDlnaProfilesHelp": "Create a custom profile to target a new device or override a system profile.", - "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", - "TitleDashboard": "Dashboard", - "TabHome": "Home", - "TabInfo": "Info", - "HeaderLinks": "Links", - "HeaderSystemPaths": "System Paths", - "LinkCommunity": "Community", - "LinkGithub": "Github", - "LinkApiDocumentation": "Api Documentation", - "LabelFriendlyServerName": "Friendly server name:", - "LabelFriendlyServerNameHelp": "This name will be used to identify this server. If left blank, the computer name will be used.", - "LabelPreferredDisplayLanguage": "Preferred display language", - "LabelPreferredDisplayLanguageHelp": "Translating Media Browser is an ongoing project and is not yet complete.", - "LabelReadHowYouCanContribute": "Read about how you can contribute.", - "HeaderNewCollection": "New Collection", - "HeaderAddToCollection": "Add to Collection", - "ButtonSubmit": "Submit", - "NewCollectionNameExample": "Example: Star Wars Collection", - "OptionSearchForInternetMetadata": "Search the internet for artwork and metadata", - "ButtonCreate": "Create", - "LabelHttpServerPortNumber": "Http server port number:", - "LabelWebSocketPortNumber": "Web socket port number:", - "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", - "LabelEnableAutomaticPortHelp": "UPnP allows automated router configuration for remote access. This may not work with some router models.", - "LabelExternalDDNS": "External DDNS:", - "LabelExternalDDNSHelp": "If you have a dynamic DNS enter it here. Media Browser apps will use it when connecting remotely.", - "TabResume": "Resume", - "TabWeather": "Weather", - "TitleAppSettings": "App Settings", - "LabelMinResumePercentage": "Min resume percentage:", - "LabelMaxResumePercentage": "Max resume percentage:", - "LabelMinResumeDuration": "Min resume duration (seconds):", - "LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time", - "LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time", - "LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable", - "TitleAutoOrganize": "Auto-Organize", - "TabActivityLog": "Activity Log", - "HeaderName": "Name", - "HeaderDate": "Date", - "HeaderSource": "Source", - "HeaderStatus": "Status", - "HeaderDestination": "Destination", - "HeaderProgram": "Program", - "HeaderClients": "Clients", - "LabelCompleted": "Completed", - "LabelFailed": "Failed", - "LabelSkipped": "Skipped", - "HeaderEpisodeOrganization": "Episode Organization", - "LabelSeries": "Series:", - "LabelSeasonNumber": "Season number:", - "LabelEpisodeNumber": "Episode number:", - "LabelEndingEpisodeNumber": "Ending episode number:", - "LabelEndingEpisodeNumberHelp": "Only required for multi-episode files", - "HeaderSupportTheTeam": "Support the Media Browser Team", - "LabelSupportAmount": "Amount (USD)", - "HeaderSupportTheTeamHelp": "Help ensure the continued development of this project by donating. A portion of all donations will be contributed to other free tools we depend on.", - "ButtonEnterSupporterKey": "Enter supporter key", - "DonationNextStep": "Once complete, please return and enter your supporter key, which you will receive by email.", - "AutoOrganizeHelp": "Auto-organize monitors your download folders for new files and moves them to your media directories.", - "AutoOrganizeTvHelp": "TV file organizing will only add episodes to existing series. It will not create new series folders.", - "OptionEnableEpisodeOrganization": "Enable new episode organization", - "LabelWatchFolder": "Watch folder:", - "LabelWatchFolderHelp": "The server will poll this folder during the 'Organize new media files' scheduled task.", - "ButtonViewScheduledTasks": "View scheduled tasks", - "LabelMinFileSizeForOrganize": "Minimum file size (MB):", - "LabelMinFileSizeForOrganizeHelp": "Files under this size will be ignored.", - "LabelSeasonFolderPattern": "Season folder pattern:", - "LabelSeasonZeroFolderName": "Season zero folder name:", - "HeaderEpisodeFilePattern": "Episode file pattern", - "LabelEpisodePattern": "Episode pattern:", - "LabelMultiEpisodePattern": "Multi-Episode pattern:", - "HeaderSupportedPatterns": "Supported Patterns", - "HeaderTerm": "Term", - "HeaderPattern": "Pattern", - "HeaderResult": "Result", - "LabelDeleteEmptyFolders": "Delete empty folders after organizing", - "LabelDeleteEmptyFoldersHelp": "Enable this to keep the download directory clean.", - "LabelDeleteLeftOverFiles": "Delete left over files with the following extensions:", - "LabelDeleteLeftOverFilesHelp": "Separate with ;. For example: .nfo;.txt", - "OptionOverwriteExistingEpisodes": "Overwrite existing episodes", - "LabelTransferMethod": "Transfer method", - "OptionCopy": "Copy", - "OptionMove": "Move", - "LabelTransferMethodHelp": "Copy or move files from the watch folder", - "HeaderLatestNews": "Latest News", - "HeaderHelpImproveMediaBrowser": "Help Improve Media Browser", - "HeaderRunningTasks": "Running Tasks", - "HeaderActiveDevices": "Active Devices", - "HeaderPendingInstallations": "Pending Installations", - "HeaerServerInformation": "Server Information", - "ButtonRestartNow": "Restart Now", - "ButtonRestart": "Restart", - "ButtonShutdown": "Shutdown", - "ButtonUpdateNow": "Update Now", - "PleaseUpdateManually": "Please shutdown the server and update manually.", - "NewServerVersionAvailable": "A new version of Media Browser Server is available!", - "ServerUpToDate": "Media Browser Server is up to date", - "ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.", - "LabelComponentsUpdated": "The following components have been installed or updated:", - "MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates.", - "LabelDownMixAudioScale": "Audio boost when downmixing:", - "LabelDownMixAudioScaleHelp": "Boost audio when downmixing. Set to 1 to preserve original volume value.", - "ButtonLinkKeys": "Link Keys", - "LabelOldSupporterKey": "Old supporter key", - "LabelNewSupporterKey": "New supporter key", - "HeaderMultipleKeyLinking": "Multiple Key Linking", - "MultipleKeyLinkingHelp": "If you have more than one supporter key, use this form to link the old key's registrations with your new one.", - "LabelCurrentEmailAddress": "Current email address", - "LabelCurrentEmailAddressHelp": "The current email address to which your new key was sent.", - "HeaderForgotKey": "Forgot Key", - "LabelEmailAddress": "Email address", - "LabelSupporterEmailAddress": "The email address that was used to purchase the key.", - "ButtonRetrieveKey": "Retrieve Key", - "LabelSupporterKey": "Supporter Key (paste from email)", - "LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Media Browser.", - "MessageInvalidKey": "Supporter key is missing or invalid.", - "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be a Media Browser Supporter. Please donate and support the continued development of the core product. Thank you.", - "HeaderDisplaySettings": "Display Settings", - "TabPlayTo": "Play To", - "LabelEnableDlnaServer": "Enable Dlna server", - "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Media Browser content.", - "LabelEnableBlastAliveMessages": "Blast alive messages", - "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", - "LabelBlastMessageInterval": "Alive message interval (seconds)", - "LabelBlastMessageIntervalHelp": "Determines the duration in seconds between server alive messages.", - "LabelDefaultUser": "Default user:", - "LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.", - "TitleDlna": "DLNA", - "TitleChannels": "Channels", - "HeaderServerSettings": "Server Settings", - "LabelWeatherDisplayLocation": "Weather display location:", - "LabelWeatherDisplayLocationHelp": "US zip code / City, State, Country / City, Country", - "LabelWeatherDisplayUnit": "Weather display unit:", - "OptionCelsius": "Celsius", - "OptionFahrenheit": "Fahrenheit", - "HeaderRequireManualLogin": "Require manual username entry for:", - "HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.", - "OptionOtherApps": "Other apps", - "OptionMobileApps": "Mobile apps", - "HeaderNotificationList": "Click on a notification to configure it's sending options.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)", - "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.", - "NotificationOptionServerRestartRequired": "Server restart required", - "LabelNotificationEnabled": "Enable this notification", - "LabelMonitorUsers": "Monitor activity from:", - "LabelSendNotificationToUsers": "Send the notification to:", - "UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.", - "LabelUseNotificationServices": "Use the following services:", - "CategoryUser": "User", - "CategorySystem": "System", - "CategoryApplication": "Application", - "CategoryPlugin": "Plugin", - "LabelMessageTitle": "Message title:", - "LabelAvailableTokens": "Available tokens:", - "AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.", - "OptionAllUsers": "All users", - "OptionAdminUsers": "Administrators", - "OptionCustomUsers": "Custom", - "ButtonArrowUp": "Up", - "ButtonArrowDown": "Down", - "ButtonArrowLeft": "Left", - "ButtonArrowRight": "Right", - "ButtonBack": "Back", - "ButtonInfo": "Info", - "ButtonOsd": "On screen display", - "ButtonPageUp": "Page Up", - "ButtonPageDown": "Page Down", - "PageAbbreviation": "PG", - "ButtonHome": "Home", - "ButtonSearch": "Search", - "ButtonSettings": "Settings", - "ButtonTakeScreenshot": "Capture Screenshot", - "ButtonLetterUp": "Letter Up", - "ButtonLetterDown": "Letter Down", - "PageButtonAbbreviation": "PG", - "LetterButtonAbbreviation": "A", - "TabNowPlaying": "Now Playing", - "TabNavigation": "Navigation", - "TabControls": "Controls", - "ButtonFullscreen": "Toggle fullscreen", - "ButtonScenes": "Scenes", - "ButtonSubtitles": "Subtitles", - "ButtonAudioTracks": "Audio tracks", - "ButtonPreviousTrack": "Previous track", - "ButtonNextTrack": "Next track", - "ButtonStop": "Stop", - "ButtonPlay": "Play", - "ButtonPause": "Pause", - "LabelGroupMoviesIntoCollections": "Group movies into collections", - "LabelGroupMoviesIntoCollectionsHelp": "When displaying movie lists, movies belonging to a collection will be displayed as one grouped item.", - "NotificationOptionPluginError": "Plugin failure", - "ButtonVolumeUp": "Volume up", - "ButtonVolumeDown": "Volume down", - "ButtonMute": "Mute", - "HeaderLatestMedia": "Latest Media", - "OptionNoSubtitles": "No Subtitles", - "OptionSpecialFeatures": "Special Features", - "HeaderCollections": "Collections", - "HeaderChannels": "Channels", - "LabelProfileCodecsHelp": "Separated by comma. This can be left empty to apply to all codecs.", - "LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.", - "HeaderResponseProfile": "Response Profile", - "LabelType": "Type:", - "LabelProfileContainer": "Container:", - "LabelProfileVideoCodecs": "Video codecs:", - "LabelProfileAudioCodecs": "Audio codecs:", - "LabelProfileCodecs": "Codecs:", - "HeaderDirectPlayProfile": "Direct Play Profile", - "HeaderTranscodingProfile": "Transcoding Profile", - "HeaderCodecProfile": "Codec Profile", - "HeaderCodecProfileHelp": "Define additional conditions that must be met in order for a codec to be direct played.", - "HeaderContainerProfile": "Container Profile", - "HeaderContainerProfileHelp": "Define additional conditions that must be met in order for a file to be direct played.", - "OptionProfileVideo": "Video", - "OptionProfileAudio": "Audio", - "OptionProfileVideoAudio": "Video Audio", - "OptionProfilePhoto": "Photo", - "LabelUserLibrary": "User library:", - "LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.", - "OptionPlainStorageFolders": "Display all folders as plain storage folders", - "OptionPlainStorageFoldersHelp": "If enabled, all folders are represented in DIDL as \"object.container.storageFolder\" instead of a more specific type, such as \"object.container.person.musicArtist\".", - "OptionPlainVideoItems": "Display all videos as plain video items", - "OptionPlainVideoItemsHelp": "If enabled, all videos are represented in DIDL as \"object.item.videoItem\" instead of a more specific type, such as \"object.item.videoItem.movie\".", - "LabelSupportedMediaTypes": "Supported Media Types:", - "TabIdentification": "Identification", - "TabDirectPlay": "Direct Play", - "TabContainers": "Containers", - "TabCodecs": "Codecs", - "TabResponses": "Responses", - "HeaderProfileInformation": "Profile Information", - "LabelEmbedAlbumArtDidl": "Embed album art in Didl", - "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.", - "LabelAlbumArtPN": "Album art PN:", - "LabelAlbumArtHelp": "PN used for album art, within the dlna:profileID attribute on upnp:albumArtURI. Some clients require a specific value, regardless of the size of the image.", - "LabelAlbumArtMaxWidth": "Album art max width:", - "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", - "LabelAlbumArtMaxHeight": "Album art max height:", - "LabelAlbumArtMaxHeightHelp": "Max resolution of album art exposed via upnp:albumArtURI.", - "LabelIconMaxWidth": "Icon max width:", - "LabelIconMaxWidthHelp": "Max resolution of icons exposed via upnp:icon.", - "LabelIconMaxHeight": "Icon max height:", - "LabelIconMaxHeightHelp": "Max resolution of icons exposed via upnp:icon.", - "LabelIdentificationFieldHelp": "A case-insensitive substring or regex expression.", - "HeaderProfileServerSettingsHelp": "These values control how Media Browser will present itself to the device.", - "LabelMaxBitrate": "Max bitrate:", - "LabelMaxBitrateHelp": "Specify a max bitrate in bandwidth constrained environments, or if the device imposes it's own limit.", - "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.", - "LabelFriendlyName": "Friendly name", - "LabelManufacturer": "Manufacturer", - "LabelManufacturerUrl": "Manufacturer url", - "LabelModelName": "Model name", - "LabelModelNumber": "Model number", - "LabelModelDescription": "Model description", - "LabelModelUrl": "Model url", - "LabelSerialNumber": "Serial number", - "LabelDeviceDescription": "Device description", - "HeaderIdentificationCriteriaHelp": "Enter at least one identification criteria.", - "HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.", - "HeaderTranscodingProfileHelp": "Add transcoding profiles to indicate which formats should be used when transcoding is required.", - "HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.", - "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct play.", - "HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.", - "LabelXDlnaCap": "X-Dlna cap:", - "LabelXDlnaCapHelp": "Determines the content of the X_DLNACAP element in the urn:schemas-dlna-org:device-1-0 namespace.", - "LabelXDlnaDoc": "X-Dlna doc:", - "LabelXDlnaDocHelp": "Determines the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.", - "LabelSonyAggregationFlags": "Sony aggregation flags:", - "LabelSonyAggregationFlagsHelp": "Determines the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.", - "LabelTranscodingContainer": "Container:", - "LabelTranscodingVideoCodec": "Video codec:", - "LabelTranscodingVideoProfile": "Video profile:", - "LabelTranscodingAudioCodec": "Audio codec:", - "OptionEnableM2tsMode": "Enable M2ts mode", - "OptionEnableM2tsModeHelp": "Enable m2ts mode when encoding to mpegts.", - "OptionEstimateContentLength": "Estimate content length when transcoding", - "OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding", - "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 to enable additional chapter 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.", - "LabelPlayDefaultAudioTrack": "Play default audio track regardless of language", - "LabelSubtitlePlaybackMode": "Subtitle mode:", - "LabelDownloadLanguages": "Download languages:", - "ButtonRegister": "Register", - "LabelSkipIfAudioTrackPresent": "Skip if the default audio track matches the download language", - "LabelSkipIfAudioTrackPresentHelp": "Uncheck this to ensure all videos have subtitles, regardless of audio language.", - "HeaderSendMessage": "Send Message", - "ButtonSend": "Send", - "LabelMessageText": "Message text:", - "LabelMessageTitle": "Message title:", - "MessageNoAvailablePlugins": "No available plugins.", - "LabelDisplayPluginsFor": "Display plugins for:", - "PluginTabMediaBrowserClassic": "MB Classic", - "PluginTabMediaBrowserTheater": "MB Theater", - "LabelEpisodeName": "Episode name", - "LabelSeriesName": "Series name", - "ValueSeriesNamePeriod": "Series.name", - "ValueSeriesNameUnderscore": "Series_name", - "ValueEpisodeNamePeriod": "Episode.name", - "ValueEpisodeNameUnderscore": "Episode_name", - "LabelSeasonNumber": "Season number", - "LabelEpisodeNumber": "Episode number", - "LabelEndingEpisodeNumber": "Ending episode number", - "HeaderTypeText": "Enter Text", - "LabelTypeText": "Text", - "HeaderSearchForSubtitles": "Search for Subtitles", - "MessageNoSubtitleSearchResultsFound": "No search results founds.", - "TabDisplay": "Display", - "TabLanguages": "Languages", - "TabWebClient": "Web Client", - "LabelEnableThemeSongs": "Enable theme songs", - "LabelEnableBackdrops": "Enable backdrops", - "LabelEnableThemeSongsHelp": "If enabled, theme songs will be played in the background while browsing the library.", - "LabelEnableBackdropsHelp": "If enabled, backdrops will be displayed in the background of some pages while browsing the library.", - "HeaderHomePage": "Home Page", - "HeaderSettingsForThisDevice": "Settings for This Device", - "OptionAuto": "Auto", - "OptionYes": "Yes", - "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", - "OptionMyViewsButtons": "My views (buttons)", - "OptionMyViews": "My views", - "OptionMyViewsSmall": "My views (small)", - "OptionResumablemedia": "Resume", - "OptionLatestMedia": "Latest media", - "OptionLatestChannelMedia": "Latest channel items", - "HeaderLatestChannelItems": "Latest Channel Items", - "OptionNone": "None", - "HeaderLiveTv": "Live TV", - "HeaderReports": "Reports", - "HeaderMetadataManager": "Metadata Manager", - "HeaderPreferences": "Preferences", - "MessageLoadingChannels": "Loading channel content...", - "ButtonMarkRead": "Mark Read", - "OptionDefaultSort": "Default", - "OptionCommunityMostWatchedSort": "Most Watched", - "TabNextUp": "Next Up", - "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", - "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", - "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", - "ButtonDismiss": "Dismiss", - "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", - "ButtonEditOtherUserPreferences": "Edit this user's personal preferences.", - "LabelChannelStreamQuality": "Preferred internet stream quality:", - "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.", - "OptionBestAvailableStreamQuality": "Best available", - "LabelEnableChannelContentDownloadingFor": "Enable channel content downloading for:", - "LabelEnableChannelContentDownloadingForHelp": "Some channels support downloading content prior to viewing. Enable this in low bandwidth enviornments to download channel content during off hours. Content is downloaded as part of the channel download scheduled task.", - "LabelChannelDownloadPath": "Channel content download path:", - "LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.", - "LabelChannelDownloadAge": "Delete content after: (days)", - "LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming.", - "ChannelSettingsFormHelp": "Install channels such as Trailers and Vimeo in the plugin catalog.", - "LabelSelectCollection": "Select collection:", - "ViewTypeMovies": "Movies", - "ViewTypeTvShows": "TV", - "ViewTypeGames": "Games", - "ViewTypeMusic": "Music", - "ViewTypeBoxSets": "Collections", - "ViewTypeChannels": "Channels", - "ViewTypeLiveTV": "Live TV", - "HeaderOtherDisplaySettings": "Display Settings", - "HeaderMyViews": "My Views", - "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:", - "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", - "OptionDisplayAdultContent": "Display adult content", - "OptionLibraryFolders": "Media folders", - "TitleRemoteControl": "Remote Control", - "OptionLatestTvRecordings": "Latest recordings", - "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", - "TabXbmcMetadata": "Xbmc", - "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", - "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", - "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", - "LabelXbmcMetadataDateFormat": "Release date format:", - "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", - "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", - "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", - "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", - "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", - "LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.", - "LabelGroupChannelsIntoViews": "Display the following channels directly within my views:", - "LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.", - "LabelDisplayCollectionsView": "Display a collections view to show movie collections", - "LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs", - "LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.", - "TabServices": "Services", - "TabLogs": "Logs", - "HeaderServerLogFiles": "Server log files:", - "TabBranding": "Branding", - "HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.", - "LabelLoginDisclaimer": "Login disclaimer:", - "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", - "LabelAutomaticallyDonate": "Automatically donate this amount every six months", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", - "OptionList": "List", - "TabDashboard": "Dashboard", - "TitleServer": "Server", - "LabelCache": "Cache:", - "LabelLogs": "Logs:", - "LabelMetadata": "Metadata:", - "LabelImagesByName": "Images by name:", - "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", - "HeaderLatestMusic": "Latest Music", - "HeaderBranding": "Branding", - "HeaderApiKeys": "Api Keys", - "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Media Browser. Keys are issued by logging in with a Media Browser account, or by manually granting the application a key.", - "HeaderApiKey": "Api Key", - "HeaderApp": "App", - "HeaderDevice": "Device", - "HeaderUser": "User", - "HeaderDateIssued": "Date Issued", - "LabelChapterName": "Chapter {0}", - "HeaderNewApiKey": "New Api Key", - "LabelAppName": "App name", - "LabelAppNameExample": "Example: Sickbeard, NzbDrone", - "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser.", - "ButtonEnterSupporterKey": "Enter supporter key", - "HeaderHttpHeaders": "Http Headers", - "HeaderIdentificationHeader": "Identification Header", - "LabelValue": "Value:", - "LabelMatchType": "Match type:", - "OptionEquals": "Equals", - "OptionRegex": "Regex", - "OptionSubstring": "Substring" -} \ No newline at end of file + "LabelVisitCommunity": "Visit Community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "View Api Documentation", + "LabelBrowseLibrary": "Browse Library", + "LabelConfigureMediaBrowser": "Configure Media Browser", + "LabelOpenLibraryViewer": "Open Library Viewer", + "LabelRestartServer": "Restart Server", + "LabelShowLogWindow": "Show Log Window", + "LabelPrevious": "Previous", + "LabelFinish": "Finish", + "LabelNext": "Next", + "LabelYoureDone": "You're Done!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "TitleMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click Finish to view the Dashboard.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "ButtonNew": "New", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations.", + "TabPreferences": "Preferences", + "TabPassword": "Password", + "TabLibraryAccess": "Library Access", + "TabImage": "Image", + "TabProfile": "Profile", + "TabMetadata": "Metadata", + "TabImages": "Images", + "TabNotifications": "Notifications", + "TabCollectionTitles": "Titles", + "LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", + "LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons", + "HeaderVideoPlaybackSettings": "Video Playback Settings", + "HeaderPlaybackSettings": "Playback Settings", + "LabelAudioLanguagePreference": "Audio language preference:", + "LabelSubtitleLanguagePreference": "Subtitle language preference:", + "OptionDefaultSubtitles": "Default", + "OptionOnlyForcedSubtitles": "Only forced subtitles", + "OptionAlwaysPlaySubtitles": "Always play subtitles", + "OptionNoSubtitles": "None", + "OptionDefaultSubtitlesHelp": "Subtitles matching the language preference will be loaded when the audio is in a foreign language.", + "OptionOnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.", + "OptionAlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", + "OptionNoSubtitlesHelp": "Subtitles will not be loaded by default.", + "TabProfiles": "Profiles", + "TabSecurity": "Security", + "ButtonAddUser": "Add User", + "ButtonSave": "Save", + "ButtonResetPassword": "Reset Password", + "LabelNewPassword": "New password:", + "LabelNewPasswordConfirm": "New password confirm:", + "HeaderCreatePassword": "Create Password", + "LabelCurrentPassword": "Current password:", + "LabelMaxParentalRating": "Maximum allowed parental rating:", + "MaxParentalRatingHelp": "Content with a higher rating will be hidden from this user.", + "LibraryAccessHelp": "Select the media folders to share with this user. Administrators will be able to edit all folders using the metadata manager.", + "ChannelAccessHelp": "Select the channels to share with this user. Administrators will be able to edit all channels using the metadata manager.", + "ButtonDeleteImage": "Delete Image", + "LabelSelectUsers": "Select users:", + "ButtonUpload": "Upload", + "HeaderUploadNewImage": "Upload New Image", + "LabelDropImageHere": "Drop Image Here", + "ImageUploadAspectRatioHelp": "1:1 Aspect Ratio Recommended. JPG/PNG only.", + "MessageNothingHere": "Nothing here.", + "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", + "TabSuggested": "Suggested", + "TabLatest": "Latest", + "TabUpcoming": "Upcoming", + "TabShows": "Shows", + "TabEpisodes": "Episodes", + "TabGenres": "Genres", + "TabPeople": "People", + "TabNetworks": "Networks", + "HeaderUsers": "Users", + "HeaderFilters": "Filters:", + "ButtonFilter": "Filter", + "OptionFavorite": "Favorites", + "OptionLikes": "Likes", + "OptionDislikes": "Dislikes", + "OptionActors": "Actors", + "OptionGuestStars": "Guest Stars", + "OptionDirectors": "Directors", + "OptionWriters": "Writers", + "OptionProducers": "Producers", + "HeaderResume": "Resume", + "HeaderNextUp": "Next Up", + "NoNextUpItemsMessage": "None found. Start watching your shows!", + "HeaderLatestEpisodes": "Latest Episodes", + "HeaderPersonTypes": "Person Types:", + "TabSongs": "Songs", + "TabAlbums": "Albums", + "TabArtists": "Artists", + "TabAlbumArtists": "Album Artists", + "TabMusicVideos": "Music Videos", + "ButtonSort": "Sort", + "HeaderSortBy": "Sort By:", + "HeaderSortOrder": "Sort Order:", + "OptionPlayed": "Played", + "OptionUnplayed": "Unplayed", + "OptionAscending": "Ascending", + "OptionDescending": "Descending", + "OptionRuntime": "Runtime", + "OptionReleaseDate": "Release Date", + "OptionPlayCount": "Play Count", + "OptionDatePlayed": "Date Played", + "OptionDateAdded": "Date Added", + "OptionAlbumArtist": "Album Artist", + "OptionArtist": "Artist", + "OptionAlbum": "Album", + "OptionTrackName": "Track Name", + "OptionCommunityRating": "Community Rating", + "OptionNameSort": "Name", + "OptionFolderSort": "Folders", + "OptionBudget": "Budget", + "OptionRevenue": "Revenue", + "OptionPoster": "Poster", + "OptionBackdrop": "Backdrop", + "OptionTimeline": "Timeline", + "OptionThumb": "Thumb", + "OptionBanner": "Banner", + "OptionCriticRating": "Critic Rating", + "OptionVideoBitrate": "Video Bitrate", + "OptionResumable": "Resumable", + "ScheduledTasksHelp": "Click a task to adjust its schedule.", + "ScheduledTasksTitle": "Scheduled Tasks", + "TabMyPlugins": "My Plugins", + "TabCatalog": "Catalog", + "PluginsTitle": "Plugins", + "HeaderAutomaticUpdates": "Automatic Updates", + "HeaderNowPlaying": "Now Playing", + "HeaderLatestAlbums": "Latest Albums", + "HeaderLatestSongs": "Latest Songs", + "HeaderRecentlyPlayed": "Recently Played", + "HeaderFrequentlyPlayed": "Frequently Played", + "DevBuildWarning": "Dev builds are the bleeding edge. Released often, these build have not been tested. The application may crash and entire features may not work at all.", + "LabelVideoType": "Video Type:", + "OptionBluray": "Bluray", + "OptionDvd": "Dvd", + "OptionIso": "Iso", + "Option3D": "3D", + "LabelFeatures": "Features:", + "LabelService": "Service:", + "LabelStatus": "Status:", + "LabelVersion": "Version:", + "LabelLastResult": "Last result:", + "OptionHasSubtitles": "Subtitles", + "OptionHasTrailer": "Trailer", + "OptionHasThemeSong": "Theme Song", + "OptionHasThemeVideo": "Theme Video", + "TabMovies": "Movies", + "TabStudios": "Studios", + "TabTrailers": "Trailers", + "HeaderLatestMovies": "Latest Movies", + "HeaderLatestTrailers": "Latest Trailers", + "OptionHasSpecialFeatures": "Special Features", + "OptionImdbRating": "IMDb Rating", + "OptionParentalRating": "Parental Rating", + "OptionPremiereDate": "Premiere Date", + "TabBasic": "Basic", + "TabAdvanced": "Advanced", + "HeaderStatus": "Status", + "OptionContinuing": "Continuing", + "OptionEnded": "Ended", + "HeaderAirDays": "Air Days", + "OptionSunday": "Sunday", + "OptionMonday": "Monday", + "OptionTuesday": "Tuesday", + "OptionWednesday": "Wednesday", + "OptionThursday": "Thursday", + "OptionFriday": "Friday", + "OptionSaturday": "Saturday", + "HeaderManagement": "Management", + "LabelManagement": "Management:", + "OptionMissingImdbId": "Missing IMDb Id", + "OptionMissingTvdbId": "Missing TheTVDB Id", + "OptionMissingOverview": "Missing Overview", + "OptionFileMetadataYearMismatch": "File/Metadata Years Mismatched", + "TabGeneral": "General", + "TitleSupport": "Support", + "TabLog": "Log", + "TabAbout": "About", + "TabSupporterKey": "Supporter Key", + "TabBecomeSupporter": "Become a Supporter", + "MediaBrowserHasCommunity": "Media Browser has a thriving community of users and contributors.", + "CheckoutKnowledgeBase": "Check out our knowledge base to help you get the most out of Media Browser.", + "SearchKnowledgeBase": "Search the Knowledge Base", + "VisitTheCommunity": "Visit the Community", + "VisitMediaBrowserWebsite": "Visit the Media Browser Web Site", + "VisitMediaBrowserWebsiteLong": "Visit the Media Browser Web site to catch the latest news and keep up with the developer blog.", + "OptionHideUser": "Hide this user from login screens", + "OptionDisableUser": "Disable this user", + "OptionDisableUserHelp": "If disabled the server will not allow any connections from this user. Existing connections will be abruptly terminated.", + "HeaderAdvancedControl": "Advanced Control", + "LabelName": "Name:", + "OptionAllowUserToManageServer": "Allow this user to manage the server", + "HeaderFeatureAccess": "Feature Access", + "OptionAllowMediaPlayback": "Allow media playback", + "OptionAllowBrowsingLiveTv": "Allow browsing of live tv", + "OptionAllowDeleteLibraryContent": "Allow this user to delete library content", + "OptionAllowManageLiveTv": "Allow management of live tv recordings", + "OptionAllowRemoteControlOthers": "Allow this user to remote control other users", + "OptionMissingTmdbId": "Missing Tmdb Id", + "OptionIsHD": "HD", + "OptionIsSD": "SD", + "OptionMetascore": "Metascore", + "ButtonSelect": "Select", + "ButtonSearch": "Search", + "ButtonGroupVersions": "Group Versions", + "ButtonAddToCollection": "Add to Collection", + "PismoMessage": "Utilizing Pismo File Mount through a donated license.", + "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java/C# converters through a donated license.", + "HeaderCredits": "Credits", + "PleaseSupportOtherProduces": "Please support other free products we utilize:", + "VersionNumber": "Version {0}", + "TabPaths": "Paths", + "TabServer": "Server", + "TabTranscoding": "Transcoding", + "TitleAdvanced": "Advanced", + "LabelAutomaticUpdateLevel": "Automatic update level", + "OptionRelease": "Official Release", + "OptionBeta": "Beta", + "OptionDev": "Dev (Unstable)", + "LabelAllowServerAutoRestart": "Allow the server to restart automatically to apply updates", + "LabelAllowServerAutoRestartHelp": "The server will only restart during idle periods, when no users are active.", + "LabelEnableDebugLogging": "Enable debug logging", + "LabelRunServerAtStartup": "Run server at startup", + "LabelRunServerAtStartupHelp": "This will start the tray icon on windows startup. To start the windows service, uncheck this and run the service from the windows control panel. Please note that you cannot run both at the same time, so you will need to exit the tray icon before starting the service.", + "ButtonSelectDirectory": "Select Directory", + "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", + "LabelCachePath": "Cache path:", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", + "LabelImagesByNamePath": "Images by name path:", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", + "LabelMetadataPath": "Metadata path:", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", + "LabelTranscodingTempPath": "Transcoding temporary path:", + "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", + "TabBasics": "Basics", + "TabTV": "TV", + "TabGames": "Games", + "TabMusic": "Music", + "TabOthers": "Others", + "HeaderExtractChapterImagesFor": "Extract chapter images for:", + "OptionMovies": "Movies", + "OptionEpisodes": "Episodes", + "OptionOtherVideos": "Other Videos", + "TitleMetadata": "Metadata", + "LabelAutomaticUpdatesFanart": "Enable automatic updates from FanArt.tv", + "LabelAutomaticUpdatesTmdb": "Enable automatic updates from TheMovieDB.org", + "LabelAutomaticUpdatesTvdb": "Enable automatic updates from TheTVDB.com", + "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", + "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", + "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelMetadataDownloadLanguage": "Preferred download language:", + "ButtonAutoScroll": "Auto-scroll", + "LabelImageSavingConvention": "Image saving convention:", + "LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", + "OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex", + "OptionImageSavingStandard": "Standard - MB2", + "ButtonSignIn": "Sign In", + "TitleSignIn": "Sign In", + "HeaderPleaseSignIn": "Please sign in", + "LabelUser": "User:", + "LabelPassword": "Password:", + "ButtonManualLogin": "Manual Login", + "PasswordLocalhostMessage": "Passwords are not required when logging in from localhost.", + "TabGuide": "Guide", + "TabChannels": "Channels", + "TabCollections": "Collections", + "HeaderChannels": "Channels", + "TabRecordings": "Recordings", + "TabScheduled": "Scheduled", + "TabSeries": "Series", + "TabFavorites": "Favorites", + "TabMyLibrary": "My Library", + "ButtonCancelRecording": "Cancel Recording", + "HeaderPrePostPadding": "Pre/Post Padding", + "LabelPrePaddingMinutes": "Pre-padding minutes:", + "OptionPrePaddingRequired": "Pre-padding is required in order to record.", + "LabelPostPaddingMinutes": "Post-padding minutes:", + "OptionPostPaddingRequired": "Post-padding is required in order to record.", + "HeaderWhatsOnTV": "What's On", + "HeaderUpcomingTV": "Upcoming TV", + "TabStatus": "Status", + "TabSettings": "Settings", + "ButtonRefreshGuideData": "Refresh Guide Data", + "OptionPriority": "Priority", + "OptionRecordOnAllChannels": "Record program on all channels", + "OptionRecordAnytime": "Record program at any time", + "OptionRecordOnlyNewEpisodes": "Record only new episodes", + "HeaderDays": "Days", + "HeaderActiveRecordings": "Active Recordings", + "HeaderLatestRecordings": "Latest Recordings", + "HeaderAllRecordings": "All Recordings", + "ButtonPlay": "Play", + "ButtonEdit": "Edit", + "ButtonRecord": "Record", + "ButtonDelete": "Delete", + "ButtonRemove": "Remove", + "OptionRecordSeries": "Record Series", + "HeaderDetails": "Details", + "ButtonCancelRecording": "Cancel Recording", + "TitleLiveTV": "Live TV", + "LabelNumberOfGuideDays": "Number of days of guide data to download:", + "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", + "LabelActiveService": "Active Service:", + "LabelActiveServiceHelp": "Multiple tv plugins can be installed but only one can be active at a time.", + "OptionAutomatic": "Auto", + "LiveTvPluginRequired": "A Live TV service provider plugin is required in order to continue.", + "LiveTvPluginRequiredHelp": "Please install one of our available plugins, such as Next Pvr or ServerWmc.", + "LabelCustomizeOptionsPerMediaType": "Customize for media type:", + "OptionDownloadThumbImage": "Thumb", + "OptionDownloadMenuImage": "Menu", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadBoxImage": "Box", + "OptionDownloadDiscImage": "Disc", + "OptionDownloadBannerImage": "Banner", + "OptionDownloadBackImage": "Back", + "OptionDownloadArtImage": "Art", + "OptionDownloadPrimaryImage": "Primary", + "HeaderFetchImages": "Fetch Images:", + "HeaderImageSettings": "Image Settings", + "TabOther": "Other", + "LabelMaxBackdropsPerItem": "Maximum number of backdrops per item:", + "LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:", + "LabelMinBackdropDownloadWidth": "Minimum backdrop download width:", + "LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:", + "ButtonAddScheduledTaskTrigger": "Add Task Trigger", + "HeaderAddScheduledTaskTrigger": "Add Task Trigger", + "ButtonAdd": "Add", + "LabelTriggerType": "Trigger Type:", + "OptionDaily": "Daily", + "OptionWeekly": "Weekly", + "OptionOnInterval": "On an interval", + "OptionOnAppStartup": "On application startup", + "OptionAfterSystemEvent": "After a system event", + "LabelDay": "Day:", + "LabelTime": "Time:", + "LabelEvent": "Event:", + "OptionWakeFromSleep": "Wake from sleep", + "LabelEveryXMinutes": "Every:", + "HeaderTvTuners": "Tuners", + "HeaderGallery": "Gallery", + "HeaderLatestGames": "Latest Games", + "HeaderRecentlyPlayedGames": "Recently Played Games", + "TabGameSystems": "Game Systems", + "TitleMediaLibrary": "Media Library", + "TabFolders": "Folders", + "TabPathSubstitution": "Path Substitution", + "LabelSeasonZeroDisplayName": "Season 0 display name:", + "LabelEnableRealtimeMonitor": "Enable real time monitoring", + "LabelEnableRealtimeMonitorHelp": "Changes will be processed immediately, on supported file systems.", + "ButtonScanLibrary": "Scan Library", + "HeaderNumberOfPlayers": "Players:", + "OptionAnyNumberOfPlayers": "Any", + "Option1Player": "1+", + "Option2Player": "2+", + "Option3Player": "3+", + "Option4Player": "4+", + "HeaderMediaFolders": "Media Folders", + "HeaderThemeVideos": "Theme Videos", + "HeaderThemeSongs": "Theme Songs", + "HeaderScenes": "Scenes", + "HeaderAwardsAndReviews": "Awards and Reviews", + "HeaderSoundtracks": "Soundtracks", + "HeaderMusicVideos": "Music Videos", + "HeaderSpecialFeatures": "Special Features", + "HeaderCastCrew": "Cast & Crew", + "HeaderAdditionalParts": "Additional Parts", + "ButtonSplitVersionsApart": "Split Versions Apart", + "ButtonPlayTrailer": "Trailer", + "LabelMissing": "Missing", + "LabelOffline": "Offline", + "PathSubstitutionHelp": "Path substitutions are used for mapping a path on the server to a path that clients are able to access. By allowing clients direct access to media on the server they may be able to play them directly over the network and avoid using server resources to stream and transcode them.", + "HeaderFrom": "From", + "HeaderTo": "To", + "LabelFrom": "From:", + "LabelFromHelp": "Example: D:\\Movies (on the server)", + "LabelTo": "To:", + "LabelToHelp": "Example: \\\\MyServer\\Movies (a path clients can access)", + "ButtonAddPathSubstitution": "Add Substitution", + "OptionSpecialEpisode": "Specials", + "OptionMissingEpisode": "Missing Episodes", + "OptionUnairedEpisode": "Unaired Episodes", + "OptionEpisodeSortName": "Episode Sort Name", + "OptionSeriesSortName": "Series Name", + "OptionTvdbRating": "Tvdb Rating", + "HeaderTranscodingQualityPreference": "Transcoding Quality Preference:", + "OptionAutomaticTranscodingHelp": "The server will decide quality and speed", + "OptionHighSpeedTranscodingHelp": "Lower quality, but faster encoding", + "OptionHighQualityTranscodingHelp": "Higher quality, but slower encoding", + "OptionMaxQualityTranscodingHelp": "Best quality with slower encoding and high CPU usage", + "OptionHighSpeedTranscoding": "Higher speed", + "OptionHighQualityTranscoding": "Higher quality", + "OptionMaxQualityTranscoding": "Max quality", + "OptionEnableDebugTranscodingLogging": "Enable debug transcoding logging", + "OptionEnableDebugTranscodingLoggingHelp": "This will create very large log files and is only recommended as needed for troubleshooting purposes.", + "OptionEnableDebugTranscodingLogging": "Enable debug transcoding logging", + "OptionUpscaling": "Allow clients to request upscaled video", + "OptionUpscalingHelp": "In some cases this will result in improved video quality but will increase CPU usage.", + "EditCollectionItemsHelp": "Add or remove any movies, series, albums, books or games you wish to group within this collection.", + "HeaderAddTitles": "Add Titles", + "LabelEnableDlnaPlayTo": "Enable DLNA Play To", + "LabelEnableDlnaPlayToHelp": "Media Browser can detect devices within your network and offer the ability to remote control them.", + "LabelEnableDlnaDebugLogging": "Enable DLNA debug logging", + "LabelEnableDlnaDebugLoggingHelp": "This will create large log files and should only be used as needed for troubleshooting purposes.", + "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval (seconds)", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Media Browser.", + "HeaderCustomDlnaProfiles": "Custom Profiles", + "HeaderSystemDlnaProfiles": "System Profiles", + "CustomDlnaProfilesHelp": "Create a custom profile to target a new device or override a system profile.", + "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", + "TitleDashboard": "Dashboard", + "TabHome": "Home", + "TabInfo": "Info", + "HeaderLinks": "Links", + "HeaderSystemPaths": "System Paths", + "LinkCommunity": "Community", + "LinkGithub": "Github", + "LinkApiDocumentation": "Api Documentation", + "LabelFriendlyServerName": "Friendly server name:", + "LabelFriendlyServerNameHelp": "This name will be used to identify this server. If left blank, the computer name will be used.", + "LabelPreferredDisplayLanguage": "Preferred display language", + "LabelPreferredDisplayLanguageHelp": "Translating Media Browser is an ongoing project and is not yet complete.", + "LabelReadHowYouCanContribute": "Read about how you can contribute.", + "HeaderNewCollection": "New Collection", + "HeaderAddToCollection": "Add to Collection", + "ButtonSubmit": "Submit", + "NewCollectionNameExample": "Example: Star Wars Collection", + "OptionSearchForInternetMetadata": "Search the internet for artwork and metadata", + "ButtonCreate": "Create", + "LabelHttpServerPortNumber": "Http server port number:", + "LabelWebSocketPortNumber": "Web socket port number:", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortHelp": "UPnP allows automated router configuration for remote access. This may not work with some router models.", + "LabelExternalDDNS": "External DDNS:", + "LabelExternalDDNSHelp": "If you have a dynamic DNS enter it here. Media Browser apps will use it when connecting remotely.", + "TabResume": "Resume", + "TabWeather": "Weather", + "TitleAppSettings": "App Settings", + "LabelMinResumePercentage": "Min resume percentage:", + "LabelMaxResumePercentage": "Max resume percentage:", + "LabelMinResumeDuration": "Min resume duration (seconds):", + "LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time", + "LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time", + "LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable", + "TitleAutoOrganize": "Auto-Organize", + "TabActivityLog": "Activity Log", + "HeaderName": "Name", + "HeaderDate": "Date", + "HeaderSource": "Source", + "HeaderStatus": "Status", + "HeaderDestination": "Destination", + "HeaderProgram": "Program", + "HeaderClients": "Clients", + "LabelCompleted": "Completed", + "LabelFailed": "Failed", + "LabelSkipped": "Skipped", + "HeaderEpisodeOrganization": "Episode Organization", + "LabelSeries": "Series:", + "LabelSeasonNumber": "Season number:", + "LabelEpisodeNumber": "Episode number:", + "LabelEndingEpisodeNumber": "Ending episode number:", + "LabelEndingEpisodeNumberHelp": "Only required for multi-episode files", + "HeaderSupportTheTeam": "Support the Media Browser Team", + "LabelSupportAmount": "Amount (USD)", + "HeaderSupportTheTeamHelp": "Help ensure the continued development of this project by donating. A portion of all donations will be contributed to other free tools we depend on.", + "ButtonEnterSupporterKey": "Enter supporter key", + "DonationNextStep": "Once complete, please return and enter your supporter key, which you will receive by email.", + "AutoOrganizeHelp": "Auto-organize monitors your download folders for new files and moves them to your media directories.", + "AutoOrganizeTvHelp": "TV file organizing will only add episodes to existing series. It will not create new series folders.", + "OptionEnableEpisodeOrganization": "Enable new episode organization", + "LabelWatchFolder": "Watch folder:", + "LabelWatchFolderHelp": "The server will poll this folder during the 'Organize new media files' scheduled task.", + "ButtonViewScheduledTasks": "View scheduled tasks", + "LabelMinFileSizeForOrganize": "Minimum file size (MB):", + "LabelMinFileSizeForOrganizeHelp": "Files under this size will be ignored.", + "LabelSeasonFolderPattern": "Season folder pattern:", + "LabelSeasonZeroFolderName": "Season zero folder name:", + "HeaderEpisodeFilePattern": "Episode file pattern", + "LabelEpisodePattern": "Episode pattern:", + "LabelMultiEpisodePattern": "Multi-Episode pattern:", + "HeaderSupportedPatterns": "Supported Patterns", + "HeaderTerm": "Term", + "HeaderPattern": "Pattern", + "HeaderResult": "Result", + "LabelDeleteEmptyFolders": "Delete empty folders after organizing", + "LabelDeleteEmptyFoldersHelp": "Enable this to keep the download directory clean.", + "LabelDeleteLeftOverFiles": "Delete left over files with the following extensions:", + "LabelDeleteLeftOverFilesHelp": "Separate with ;. For example: .nfo;.txt", + "OptionOverwriteExistingEpisodes": "Overwrite existing episodes", + "LabelTransferMethod": "Transfer method", + "OptionCopy": "Copy", + "OptionMove": "Move", + "LabelTransferMethodHelp": "Copy or move files from the watch folder", + "HeaderLatestNews": "Latest News", + "HeaderHelpImproveMediaBrowser": "Help Improve Media Browser", + "HeaderRunningTasks": "Running Tasks", + "HeaderActiveDevices": "Active Devices", + "HeaderPendingInstallations": "Pending Installations", + "HeaerServerInformation": "Server Information", + "ButtonRestartNow": "Restart Now", + "ButtonRestart": "Restart", + "ButtonShutdown": "Shutdown", + "ButtonUpdateNow": "Update Now", + "PleaseUpdateManually": "Please shutdown the server and update manually.", + "NewServerVersionAvailable": "A new version of Media Browser Server is available!", + "ServerUpToDate": "Media Browser Server is up to date", + "ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.", + "LabelComponentsUpdated": "The following components have been installed or updated:", + "MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates.", + "LabelDownMixAudioScale": "Audio boost when downmixing:", + "LabelDownMixAudioScaleHelp": "Boost audio when downmixing. Set to 1 to preserve original volume value.", + "ButtonLinkKeys": "Link Keys", + "LabelOldSupporterKey": "Old supporter key", + "LabelNewSupporterKey": "New supporter key", + "HeaderMultipleKeyLinking": "Multiple Key Linking", + "MultipleKeyLinkingHelp": "If you have more than one supporter key, use this form to link the old key's registrations with your new one.", + "LabelCurrentEmailAddress": "Current email address", + "LabelCurrentEmailAddressHelp": "The current email address to which your new key was sent.", + "HeaderForgotKey": "Forgot Key", + "LabelEmailAddress": "Email address", + "LabelSupporterEmailAddress": "The email address that was used to purchase the key.", + "ButtonRetrieveKey": "Retrieve Key", + "LabelSupporterKey": "Supporter Key (paste from email)", + "LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Media Browser.", + "MessageInvalidKey": "Supporter key is missing or invalid.", + "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be a Media Browser Supporter. Please donate and support the continued development of the core product. Thank you.", + "HeaderDisplaySettings": "Display Settings", + "TabPlayTo": "Play To", + "LabelEnableDlnaServer": "Enable Dlna server", + "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Media Browser content.", + "LabelEnableBlastAliveMessages": "Blast alive messages", + "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", + "LabelBlastMessageInterval": "Alive message interval (seconds)", + "LabelBlastMessageIntervalHelp": "Determines the duration in seconds between server alive messages.", + "LabelDefaultUser": "Default user:", + "LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.", + "TitleDlna": "DLNA", + "TitleChannels": "Channels", + "HeaderServerSettings": "Server Settings", + "LabelWeatherDisplayLocation": "Weather display location:", + "LabelWeatherDisplayLocationHelp": "US zip code / City, State, Country / City, Country", + "LabelWeatherDisplayUnit": "Weather display unit:", + "OptionCelsius": "Celsius", + "OptionFahrenheit": "Fahrenheit", + "HeaderRequireManualLogin": "Require manual username entry for:", + "HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.", + "OptionOtherApps": "Other apps", + "OptionMobileApps": "Mobile apps", + "HeaderNotificationList": "Click on a notification to configure it's sending options.", + "NotificationOptionApplicationUpdateAvailable": "Application update available", + "NotificationOptionApplicationUpdateInstalled": "Application update installed", + "NotificationOptionPluginUpdateInstalled": "Plugin update installed", + "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionPluginUninstalled": "Plugin uninstalled", + "NotificationOptionVideoPlayback": "Video playback started", + "NotificationOptionAudioPlayback": "Audio playback started", + "NotificationOptionGamePlayback": "Game playback started", + "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", + "NotificationOptionGamePlaybackStopped": "Game playback stopped", + "NotificationOptionTaskFailed": "Scheduled task failure", + "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionNewLibraryContent": "New content added", + "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)", + "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.", + "NotificationOptionServerRestartRequired": "Server restart required", + "LabelNotificationEnabled": "Enable this notification", + "LabelMonitorUsers": "Monitor activity from:", + "LabelSendNotificationToUsers": "Send the notification to:", + "UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.", + "LabelUseNotificationServices": "Use the following services:", + "CategoryUser": "User", + "CategorySystem": "System", + "CategoryApplication": "Application", + "CategoryPlugin": "Plugin", + "LabelMessageTitle": "Message title:", + "LabelAvailableTokens": "Available tokens:", + "AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.", + "OptionAllUsers": "All users", + "OptionAdminUsers": "Administrators", + "OptionCustomUsers": "Custom", + "ButtonArrowUp": "Up", + "ButtonArrowDown": "Down", + "ButtonArrowLeft": "Left", + "ButtonArrowRight": "Right", + "ButtonBack": "Back", + "ButtonInfo": "Info", + "ButtonOsd": "On screen display", + "ButtonPageUp": "Page Up", + "ButtonPageDown": "Page Down", + "PageAbbreviation": "PG", + "ButtonHome": "Home", + "ButtonSearch": "Search", + "ButtonSettings": "Settings", + "ButtonTakeScreenshot": "Capture Screenshot", + "ButtonLetterUp": "Letter Up", + "ButtonLetterDown": "Letter Down", + "PageButtonAbbreviation": "PG", + "LetterButtonAbbreviation": "A", + "TabNowPlaying": "Now Playing", + "TabNavigation": "Navigation", + "TabControls": "Controls", + "ButtonFullscreen": "Toggle fullscreen", + "ButtonScenes": "Scenes", + "ButtonSubtitles": "Subtitles", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", + "ButtonStop": "Stop", + "ButtonPlay": "Play", + "ButtonPause": "Pause", + "LabelGroupMoviesIntoCollections": "Group movies into collections", + "LabelGroupMoviesIntoCollectionsHelp": "When displaying movie lists, movies belonging to a collection will be displayed as one grouped item.", + "NotificationOptionPluginError": "Plugin failure", + "ButtonVolumeUp": "Volume up", + "ButtonVolumeDown": "Volume down", + "ButtonMute": "Mute", + "HeaderLatestMedia": "Latest Media", + "OptionNoSubtitles": "No Subtitles", + "OptionSpecialFeatures": "Special Features", + "HeaderCollections": "Collections", + "HeaderChannels": "Channels", + "LabelProfileCodecsHelp": "Separated by comma. This can be left empty to apply to all codecs.", + "LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.", + "HeaderResponseProfile": "Response Profile", + "LabelType": "Type:", + "LabelProfileContainer": "Container:", + "LabelProfileVideoCodecs": "Video codecs:", + "LabelProfileAudioCodecs": "Audio codecs:", + "LabelProfileCodecs": "Codecs:", + "HeaderDirectPlayProfile": "Direct Play Profile", + "HeaderTranscodingProfile": "Transcoding Profile", + "HeaderCodecProfile": "Codec Profile", + "HeaderCodecProfileHelp": "Define additional conditions that must be met in order for a codec to be direct played.", + "HeaderContainerProfile": "Container Profile", + "HeaderContainerProfileHelp": "Define additional conditions that must be met in order for a file to be direct played.", + "OptionProfileVideo": "Video", + "OptionProfileAudio": "Audio", + "OptionProfileVideoAudio": "Video Audio", + "OptionProfilePhoto": "Photo", + "LabelUserLibrary": "User library:", + "LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.", + "OptionPlainStorageFolders": "Display all folders as plain storage folders", + "OptionPlainStorageFoldersHelp": "If enabled, all folders are represented in DIDL as \"object.container.storageFolder\" instead of a more specific type, such as \"object.container.person.musicArtist\".", + "OptionPlainVideoItems": "Display all videos as plain video items", + "OptionPlainVideoItemsHelp": "If enabled, all videos are represented in DIDL as \"object.item.videoItem\" instead of a more specific type, such as \"object.item.videoItem.movie\".", + "LabelSupportedMediaTypes": "Supported Media Types:", + "TabIdentification": "Identification", + "TabDirectPlay": "Direct Play", + "TabContainers": "Containers", + "TabCodecs": "Codecs", + "TabResponses": "Responses", + "HeaderProfileInformation": "Profile Information", + "LabelEmbedAlbumArtDidl": "Embed album art in Didl", + "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.", + "LabelAlbumArtPN": "Album art PN:", + "LabelAlbumArtHelp": "PN used for album art, within the dlna:profileID attribute on upnp:albumArtURI. Some clients require a specific value, regardless of the size of the image.", + "LabelAlbumArtMaxWidth": "Album art max width:", + "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Album art max height:", + "LabelAlbumArtMaxHeightHelp": "Max resolution of album art exposed via upnp:albumArtURI.", + "LabelIconMaxWidth": "Icon max width:", + "LabelIconMaxWidthHelp": "Max resolution of icons exposed via upnp:icon.", + "LabelIconMaxHeight": "Icon max height:", + "LabelIconMaxHeightHelp": "Max resolution of icons exposed via upnp:icon.", + "LabelIdentificationFieldHelp": "A case-insensitive substring or regex expression.", + "HeaderProfileServerSettingsHelp": "These values control how Media Browser will present itself to the device.", + "LabelMaxBitrate": "Max bitrate:", + "LabelMaxBitrateHelp": "Specify a max bitrate in bandwidth constrained environments, or if the device imposes it's own limit.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.", + "LabelFriendlyName": "Friendly name", + "LabelManufacturer": "Manufacturer", + "LabelManufacturerUrl": "Manufacturer url", + "LabelModelName": "Model name", + "LabelModelNumber": "Model number", + "LabelModelDescription": "Model description", + "LabelModelUrl": "Model url", + "LabelSerialNumber": "Serial number", + "LabelDeviceDescription": "Device description", + "HeaderIdentificationCriteriaHelp": "Enter at least one identification criteria.", + "HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.", + "HeaderTranscodingProfileHelp": "Add transcoding profiles to indicate which formats should be used when transcoding is required.", + "HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.", + "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct play.", + "HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.", + "LabelXDlnaCap": "X-Dlna cap:", + "LabelXDlnaCapHelp": "Determines the content of the X_DLNACAP element in the urn:schemas-dlna-org:device-1-0 namespace.", + "LabelXDlnaDoc": "X-Dlna doc:", + "LabelXDlnaDocHelp": "Determines the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.", + "LabelSonyAggregationFlags": "Sony aggregation flags:", + "LabelSonyAggregationFlagsHelp": "Determines the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.", + "LabelTranscodingContainer": "Container:", + "LabelTranscodingVideoCodec": "Video codec:", + "LabelTranscodingVideoProfile": "Video profile:", + "LabelTranscodingAudioCodec": "Audio codec:", + "OptionEnableM2tsMode": "Enable M2ts mode", + "OptionEnableM2tsModeHelp": "Enable m2ts mode when encoding to mpegts.", + "OptionEstimateContentLength": "Estimate content length when transcoding", + "OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding", + "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 to enable additional chapter 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.", + "LabelPlayDefaultAudioTrack": "Play default audio track regardless of language", + "LabelSubtitlePlaybackMode": "Subtitle mode:", + "LabelDownloadLanguages": "Download languages:", + "ButtonRegister": "Register", + "LabelSkipIfAudioTrackPresent": "Skip if the default audio track matches the download language", + "LabelSkipIfAudioTrackPresentHelp": "Uncheck this to ensure all videos have subtitles, regardless of audio language.", + "HeaderSendMessage": "Send Message", + "ButtonSend": "Send", + "LabelMessageText": "Message text:", + "LabelMessageTitle": "Message title:", + "MessageNoAvailablePlugins": "No available plugins.", + "LabelDisplayPluginsFor": "Display plugins for:", + "PluginTabMediaBrowserClassic": "MB Classic", + "PluginTabMediaBrowserTheater": "MB Theater", + "LabelEpisodeName": "Episode name", + "LabelSeriesName": "Series name", + "ValueSeriesNamePeriod": "Series.name", + "ValueSeriesNameUnderscore": "Series_name", + "ValueEpisodeNamePeriod": "Episode.name", + "ValueEpisodeNameUnderscore": "Episode_name", + "LabelSeasonNumber": "Season number", + "LabelEpisodeNumber": "Episode number", + "LabelEndingEpisodeNumber": "Ending episode number", + "HeaderTypeText": "Enter Text", + "LabelTypeText": "Text", + "HeaderSearchForSubtitles": "Search for Subtitles", + "MessageNoSubtitleSearchResultsFound": "No search results founds.", + "TabDisplay": "Display", + "TabLanguages": "Languages", + "TabWebClient": "Web Client", + "LabelEnableThemeSongs": "Enable theme songs", + "LabelEnableBackdrops": "Enable backdrops", + "LabelEnableThemeSongsHelp": "If enabled, theme songs will be played in the background while browsing the library.", + "LabelEnableBackdropsHelp": "If enabled, backdrops will be displayed in the background of some pages while browsing the library.", + "HeaderHomePage": "Home Page", + "HeaderSettingsForThisDevice": "Settings for This Device", + "OptionAuto": "Auto", + "OptionYes": "Yes", + "OptionNo": "No", + "LabelHomePageSection1": "Home page section one:", + "LabelHomePageSection2": "Home page section two:", + "LabelHomePageSection3": "Home page section three:", + "LabelHomePageSection4": "Home page section four:", + "OptionMyViewsButtons": "My views (buttons)", + "OptionMyViews": "My views", + "OptionMyViewsSmall": "My views (small)", + "OptionResumablemedia": "Resume", + "OptionLatestMedia": "Latest media", + "OptionLatestChannelMedia": "Latest channel items", + "HeaderLatestChannelItems": "Latest Channel Items", + "OptionNone": "None", + "HeaderLiveTv": "Live TV", + "HeaderReports": "Reports", + "HeaderMetadataManager": "Metadata Manager", + "HeaderPreferences": "Preferences", + "MessageLoadingChannels": "Loading channel content...", + "ButtonMarkRead": "Mark Read", + "OptionDefaultSort": "Default", + "OptionCommunityMostWatchedSort": "Most Watched", + "TabNextUp": "Next Up", + "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", + "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", + "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", + "ButtonDismiss": "Dismiss", + "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", + "ButtonEditOtherUserPreferences": "Edit this user's personal preferences.", + "LabelChannelStreamQuality": "Preferred internet stream quality:", + "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.", + "OptionBestAvailableStreamQuality": "Best available", + "LabelEnableChannelContentDownloadingFor": "Enable channel content downloading for:", + "LabelEnableChannelContentDownloadingForHelp": "Some channels support downloading content prior to viewing. Enable this in low bandwidth enviornments to download channel content during off hours. Content is downloaded as part of the channel download scheduled task.", + "LabelChannelDownloadPath": "Channel content download path:", + "LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.", + "LabelChannelDownloadAge": "Delete content after: (days)", + "LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming.", + "ChannelSettingsFormHelp": "Install channels such as Trailers and Vimeo in the plugin catalog.", + "LabelSelectCollection": "Select collection:", + "ViewTypeMovies": "Movies", + "ViewTypeTvShows": "TV", + "ViewTypeGames": "Games", + "ViewTypeMusic": "Music", + "ViewTypeBoxSets": "Collections", + "ViewTypeChannels": "Channels", + "ViewTypeLiveTV": "Live TV", + "HeaderOtherDisplaySettings": "Display Settings", + "HeaderMyViews": "My Views", + "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:", + "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", + "OptionDisplayAdultContent": "Display adult content", + "OptionLibraryFolders": "Media folders", + "TitleRemoteControl": "Remote Control", + "OptionLatestTvRecordings": "Latest recordings", + "LabelProtocolInfo": "Protocol info:", + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.", + "LabelGroupChannelsIntoViews": "Display the following channels directly within my views:", + "LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.", + "LabelDisplayCollectionsView": "Display a collections view to show movie collections", + "LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs", + "LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.", + "TabServices": "Services", + "TabLogs": "Logs", + "HeaderServerLogFiles": "Server log files:", + "TabBranding": "Branding", + "HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.", + "LabelLoginDisclaimer": "Login disclaimer:", + "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", + "LabelAutomaticallyDonate": "Automatically donate this amount every six months", + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", + "HeaderLatestMusic": "Latest Music", + "HeaderBranding": "Branding", + "HeaderApiKeys": "Api Keys", + "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Media Browser. Keys are issued by logging in with a Media Browser account, or by manually granting the application a key.", + "HeaderApiKey": "Api Key", + "HeaderApp": "App", + "HeaderDevice": "Device", + "HeaderUser": "User", + "HeaderDateIssued": "Date Issued", + "LabelChapterName": "Chapter {0}", + "HeaderNewApiKey": "New Api Key", + "LabelAppName": "App name", + "LabelAppNameExample": "Example: Sickbeard, NzbDrone", + "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser.", + "ButtonEnterSupporterKey": "Enter supporter key", + "HeaderHttpHeaders": "Http Headers", + "HeaderIdentificationHeader": "Identification Header", + "LabelValue": "Value:", + "LabelMatchType": "Match type:", + "OptionEquals": "Equals", + "OptionRegex": "Regex", + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync" +} diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index 75e204f684..e28dfe231a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -199,6 +199,7 @@ "OptionFriday": "Fredag", "OptionSaturday": "L\u00f6rdag", "HeaderManagement": "Administration:", + "LabelManagement": "Management:", "OptionMissingImdbId": "IMDB-ID saknas", "OptionMissingTvdbId": "TVDB-ID saknas", "OptionMissingOverview": "Synopsis saknas", @@ -479,10 +480,10 @@ "HeaderProgram": "Program", "HeaderClients": "Klienter", "LabelCompleted": "Klar", - "LabelFailed": "Misslyckades", + "LabelFailed": "Failed", "LabelSkipped": "Hoppades \u00f6ver", "HeaderEpisodeOrganization": "Katalogisering av avsnitt", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "S\u00e4songsnummer:", "LabelEpisodeNumber": "Avsnittsnummer:", "LabelEndingEpisodeNumber": "Avslutande avsnittsnummer:", @@ -626,12 +627,12 @@ "TabNowPlaying": "Nu spelas", "TabNavigation": "Navigering", "TabControls": "Kontroller", - "ButtonFullscreen": "V\u00e4xla fullsk\u00e4rmsl\u00e4ge", + "ButtonFullscreen": "Toggle fullscreen", "ButtonScenes": "Scener", "ButtonSubtitles": "Undertexter", - "ButtonAudioTracks": "Ljudsp\u00e5r", - "ButtonPreviousTrack": "F\u00f6reg\u00e5ende sp\u00e5r", - "ButtonNextTrack": "N\u00e4sta sp\u00e5r", + "ButtonAudioTracks": "Audio tracks", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stopp", "ButtonPause": "Paus", "LabelGroupMoviesIntoCollections": "Gruppera filmer i samlingsboxar", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/vi.json b/MediaBrowser.Server.Implementations/Localization/Server/vi.json index cc5a77433a..78f308b336 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -190,7 +190,7 @@ "HeaderStatus": "Tr\u1ea1ng th\u00e1i", "OptionContinuing": "Continuing", "OptionEnded": "Ended", - "HeaderAirDays": "Air Days:", + "HeaderAirDays": "Air Days", "OptionSunday": "Ch\u1ee7 Nh\u1eadt", "OptionMonday": "Th\u1ee9 Hai", "OptionTuesday": "Tuesday", @@ -199,6 +199,7 @@ "OptionFriday": "Friday", "OptionSaturday": "Th\u1ee9 B\u1ea3y", "HeaderManagement": "Qu\u1ea3n l\u00fd:", + "LabelManagement": "Management:", "OptionMissingImdbId": "Thi\u1ebfu IMDb ID", "OptionMissingTvdbId": "Missing TheTVDB Id", "OptionMissingOverview": "Missing Overview", @@ -256,11 +257,11 @@ "ButtonSelectDirectory": "L\u1ef1a ch\u1ecdn tr\u1ef1c ti\u1ebfp", "LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.", "LabelCachePath": "Cache path:", - "LabelCachePathHelp": "This folder contains server cache files, such as images.", + "LabelCachePathHelp": "Specify a custom location for server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "Specify a custom location for downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json index c2c8f50025..e67aa9f581 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -199,6 +199,7 @@ "OptionFriday": "\u661f\u671f\u4e94", "OptionSaturday": "\u661f\u671f\u516d", "HeaderManagement": "\u7ba1\u7406\uff1a", + "LabelManagement": "Management:", "OptionMissingImdbId": "\u7f3a\u5c11IMDB\u7de8\u865f", "OptionMissingTvdbId": "\u7f3a\u5c11TheTVDB\u7de8\u865f", "OptionMissingOverview": "\u7f3a\u5c11\u6982\u8ff0", @@ -260,7 +261,7 @@ "LabelImagesByNamePath": "\u540d\u7a31\u5716\u50cf\u6587\u4ef6\u593e\u8def\u5f91\uff1a", "LabelImagesByNamePathHelp": "\u6b64\u6587\u4ef6\u593e\u5305\u542b\u6f14\u54e1\uff0c\u6b4c\u624b\uff0c\u5a92\u9ad4\u7a2e\u985e\u548c\u5de5\u4f5c\u5ba4\u7684\u5716\u50cf\u3002", "LabelMetadataPath": "\u5a92\u9ad4\u8cc7\u6599\u6587\u4ef6\u593e\u8def\u5f91\uff1a", - "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", + "LabelMetadataPathHelp": "Specify a custom location for downloaded artwork and metadata, if not saving within media folders.", "LabelTranscodingTempPath": "\u8f49\u78bc\u81e8\u6642\u8def\u5f91\uff1a", "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "\u57fa\u672c", @@ -396,7 +397,7 @@ "HeaderCastCrew": "\u62cd\u651d\u4eba\u54e1\u53ca\u6f14\u54e1", "HeaderAdditionalParts": "\u9644\u52a0\u90e8\u4efd", "ButtonSplitVersionsApart": "Split Versions Apart", - "ButtonPlayTrailer": "\u9810\u544a", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "\u7f3a\u5c11", "LabelOffline": "\u96e2\u7dda", "PathSubstitutionHelp": "Path substitutions are used for mapping a path on the server to a path that clients are able to access. By allowing clients direct access to media on the server they may be able to play them directly over the network and avoid using server resources to stream and transcode them.", @@ -874,5 +875,14 @@ "LabelMatchType": "Match type:", "OptionEquals": "Equals", "OptionRegex": "Regex", - "OptionSubstring": "Substring" + "OptionSubstring": "Substring", + "TabView": "View", + "TabSort": "Sort", + "TabFilter": "Filter", + "ButtonView": "View", + "LabelPageSize": "Item limit:", + "LabelView": "View:", + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index 6f14bb3223..a65184f615 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -249,13 +249,13 @@ namespace MediaBrowser.Server.Implementations.ServerManager _logger.Info("Sending web socket message {0}", messageType); var message = new WebSocketMessage { MessageType = messageType, Data = dataFunction() }; - var bytes = _jsonSerializer.SerializeToBytes(message); + var json = _jsonSerializer.SerializeToString(message); var tasks = connectionsList.Select(s => Task.Run(() => { try { - s.SendAsync(bytes, cancellationToken); + s.SendAsync(json, cancellationToken); } catch (OperationCanceledException) { @@ -265,7 +265,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager { _logger.ErrorException("Error sending web socket message {0} to {1}", ex, messageType, s.RemoteEndPoint); } - })); + + }, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs index 2b60f31160..3c3d7740a0 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -194,9 +194,9 @@ namespace MediaBrowser.Server.Implementations.ServerManager throw new ArgumentNullException("message"); } - var bytes = _jsonSerializer.SerializeToBytes(message); + var json = _jsonSerializer.SerializeToString(message); - return SendAsync(bytes, cancellationToken); + return SendAsync(json, cancellationToken); } /// @@ -205,24 +205,46 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// The buffer. /// The cancellation token. /// Task. - public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) + public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken) { - return SendAsync(buffer, WebSocketMessageType.Text, cancellationToken); + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Per msdn docs, attempting to send simultaneous messages will result in one failing. + // This should help us workaround that and ensure all messages get sent + await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + await _socket.SendAsync(buffer, true, cancellationToken); + } + catch (OperationCanceledException) + { + _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint); + + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint); + + throw; + } + finally + { + _sendSemaphore.Release(); + } } - /// - /// Sends a message asynchronously. - /// - /// The buffer. - /// The type. - /// The cancellation token. - /// Task. - /// buffer - public async Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken) + public async Task SendAsync(string text, CancellationToken cancellationToken) { - if (buffer == null) + if (string.IsNullOrWhiteSpace(text)) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException("text"); } cancellationToken.ThrowIfCancellationRequested(); @@ -233,7 +255,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager try { - await _socket.SendAsync(buffer, type, true, cancellationToken); + await _socket.SendAsync(text, true, cancellationToken); } catch (OperationCanceledException) { diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 3d6865f854..7c36a7c5eb 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -208,12 +208,13 @@ namespace MediaBrowser.ServerApplication private IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } - + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The application paths. /// The log manager. + /// if set to true [is running as service]. public ApplicationHost(ServerApplicationPaths applicationPaths, ILogManager logManager, bool isRunningAsService) : base(applicationPaths, logManager) { @@ -284,7 +285,6 @@ namespace MediaBrowser.ServerApplication await base.Init(progress).ConfigureAwait(false); MigrateModularConfigurations(); - ApplyDefaultMetadataSettings(); } private void PerformVersionMigration() @@ -330,21 +330,6 @@ namespace MediaBrowser.ServerApplication } } - private void ApplyDefaultMetadataSettings() - { - if (!ServerConfigurationManager.Configuration.DefaultMetadataSettingsApplied && - ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - { - // Make sure xbmc metadata is disabled for existing users. - // New users will be handled by the startup wizard. - - ServerConfigurationManager.DisableMetadataService("Xbmc Nfo"); - } - - ServerConfigurationManager.Configuration.DefaultMetadataSettingsApplied = true; - ServerConfigurationManager.SaveConfiguration(); - } - private void DeleteDeprecatedModules() { try @@ -1111,7 +1096,6 @@ namespace MediaBrowser.ServerApplication ServerAuthorization.AuthorizeServer( ServerConfigurationManager.Configuration.HttpServerPortNumber, HttpServerUrlPrefixes.First(), - ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber, UdpServerEntryPoint.PortNumber, ConfigurationManager.CommonApplicationPaths.TempDirectory); } diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 9e31fc800c..f8b03bbc25 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -194,13 +194,7 @@ namespace MediaBrowser.ServerApplication private static void BeginLog(ILogger logger, IApplicationPaths appPaths) { logger.Info("Media Browser Server started"); - logger.Info("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs())); - - logger.Info("Server: {0}", Environment.MachineName); - logger.Info("Operating system: {0}", Environment.OSVersion.ToString()); - logger.Info("Program data path: {0}", appPaths.ProgramDataPath); - - logger.Info("Application Path: {0}", appPaths.ApplicationPath); + ApplicationHost.LogEnvironmentInfo(logger, appPaths); } private static readonly TaskCompletionSource ApplicationTaskCompletionSource = new TaskCompletionSource(); diff --git a/MediaBrowser.ServerApplication/Native/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat index d762dfaf76..3504123444 100644 --- a/MediaBrowser.ServerApplication/Native/RegisterServer.bat +++ b/MediaBrowser.ServerApplication/Native/RegisterServer.bat @@ -1,7 +1,6 @@ rem %1 = http server port rem %2 = http server url rem %3 = udp server port -rem %4 = tcp server port (web socket) if [%1]==[] GOTO DONE @@ -18,11 +17,6 @@ if [%3]==[] GOTO DONE netsh advfirewall firewall delete rule name="Port %3" protocol=UDP localport=%3 netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol=UDP localport=%3 -if [%4]==[] GOTO DONE - -netsh advfirewall firewall delete rule name="Port %4" protocol=TCP localport=%4 -netsh advfirewall firewall add rule name="Port %4" dir=in action=allow protocol=TCP localport=%4 - :DONE Exit \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs index d2e5425367..e5989db3bf 100644 --- a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs +++ b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs @@ -15,10 +15,9 @@ namespace MediaBrowser.ServerApplication.Native /// /// The HTTP server port. /// The HTTP server URL prefix. - /// The web socket port. /// The UDP port. /// The temp directory. - public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory) + public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory) { Directory.CreateDirectory(tempDirectory); @@ -38,10 +37,9 @@ namespace MediaBrowser.ServerApplication.Native { FileName = tmpFile, - Arguments = string.Format("{0} {1} {2} {3}", httpServerPort, + Arguments = string.Format("{0} {1} {2}", httpServerPort, httpServerUrlPrefix, - udpPort, - webSocketPort), + udpPort), CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, diff --git a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs index cbd0ce4a18..545894b0d1 100644 --- a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs +++ b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Providers; -using MediaBrowser.Providers.Movies; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace MediaBrowser.Tests.Providers { @@ -20,12 +19,6 @@ namespace MediaBrowser.Tests.Providers { Assert.AreEqual("My Movie 2", name); Assert.AreEqual(2013, year); - name = string.Empty; - year = null; - NameParser.ParseName("2013 - My Movie 2", out name, out year); - Assert.AreEqual(2013, year); - Assert.AreEqual("My Movie 2", name); - name = string.Empty; year = null; NameParser.ParseName("My Movie 2001 (2013)", out name, out year); diff --git a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs index 725a5edcbe..104e634472 100644 --- a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs +++ b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Tests.Resolvers [TestMethod] public void TestGetEpisodeNumberFromFile() { - Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"S02E03 blah.avi", true)); + Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 02\S02E03 blah.avi", true)); Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\01x02 blah.avi", true)); Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\S01x02 blah.avi", true)); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index b50cb254fc..64c055ba00 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -446,7 +446,7 @@ namespace MediaBrowser.WebDashboard.Api var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); // jQuery + jQuery mobile - await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false); + await AppendResource(memoryStream, "thirdparty/jquery-2.1.1.min.js", newLineBytes).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.3/jquery.mobile-1.4.3.min.js", newLineBytes).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/jquery.unveil-custom.js", newLineBytes).ConfigureAwait(false); @@ -534,8 +534,6 @@ namespace MediaBrowser.WebDashboard.Api "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", - "advancedpaths.js", - "advancedserversettings.js", "metadataadvanced.js", "appsplayback.js", "autoorganizetv.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 176094b752..06a44d747c 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -89,12 +89,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -590,12 +584,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -800,6 +788,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest -- cgit v1.2.3 From 37c27a26e90b7eff62cec9e2b6a6c003e79fcbe4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 26 Jul 2014 13:30:15 -0400 Subject: added sync job database --- MediaBrowser.Api/Images/ImageByNameService.cs | 8 +- MediaBrowser.Api/Library/LibraryHelpers.cs | 2 +- MediaBrowser.Api/Sync/SyncService.cs | 67 +--- .../IO/CommonFileSystem.cs | 15 + MediaBrowser.Common/IO/IFileSystem.cs | 14 + MediaBrowser.Controller/Entities/BaseItem.cs | 8 +- MediaBrowser.Controller/Library/TVUtils.cs | 14 +- .../MediaBrowser.Controller.csproj | 1 + MediaBrowser.Controller/Sync/ISyncManager.cs | 22 +- MediaBrowser.Controller/Sync/ISyncRepository.cs | 58 +++ MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs | 112 +++++- .../ContentDirectory/ContentDirectoryBrowser.cs | 126 ++++++ MediaBrowser.Dlna/DlnaManager.cs | 2 +- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 1 + MediaBrowser.Dlna/PlayTo/Device.cs | 4 +- .../Images/CollectionFolderImageProvider.cs | 12 +- .../Images/EpisodeLocalImageProvider.cs | 14 +- .../Images/ImagesByNameImageProvider.cs | 2 +- .../Images/InternalMetadataFolderImageProvider.cs | 11 +- .../Images/LocalImageProvider.cs | 27 +- .../Providers/GameXmlProvider.cs | 2 +- .../Providers/MovieXmlProvider.cs | 10 +- .../MediaBrowser.Model.Portable.csproj | 12 +- .../MediaBrowser.Model.net35.csproj | 12 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 +- MediaBrowser.Model/Sync/SyncJob.cs | 80 +++- MediaBrowser.Model/Sync/SyncJobCreationResult.cs | 8 + MediaBrowser.Model/Sync/SyncJobItem.cs | 48 +++ MediaBrowser.Model/Sync/SyncJobQuery.cs | 10 + MediaBrowser.Model/Sync/SyncJobRequest.cs | 32 +- MediaBrowser.Model/Sync/SyncJobStatus.cs | 28 +- MediaBrowser.Model/Sync/SyncSchedule.cs | 12 - MediaBrowser.Model/Sync/SyncScheduleQuery.cs | 7 - .../BoxSets/MovieDbBoxSetProvider.cs | 6 - MediaBrowser.Providers/Manager/ImageSaver.cs | 10 +- .../MediaInfo/FFProbeProvider.cs | 2 +- .../MediaInfo/FFProbeVideoInfo.cs | 6 +- .../MediaInfo/SubtitleResolver.cs | 19 +- MediaBrowser.Providers/Movies/MovieDbProvider.cs | 2 +- .../Subtitles/SubtitleManager.cs | 2 +- .../FileOrganization/EpisodeFileOrganizer.cs | 6 +- .../HttpServer/HttpListenerHost.cs | 14 +- .../HttpServer/NetListener/HttpListenerServer.cs | 18 +- .../SocketSharp/WebSocketSharpListener.cs | 21 +- .../Library/CoreResolutionIgnoreRule.cs | 2 +- .../Library/LibraryManager.cs | 17 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 43 ++- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 15 +- .../Library/Resolvers/FolderResolver.cs | 12 +- .../Library/Resolvers/LocalTrailerResolver.cs | 12 +- .../Library/Resolvers/Movies/MovieResolver.cs | 7 +- .../Library/Resolvers/TV/SeriesResolver.cs | 12 +- .../Library/UserManager.cs | 2 +- .../Localization/JavaScript/es_MX.json | 8 +- .../Localization/JavaScript/fr.json | 2 +- .../Localization/JavaScript/nl.json | 14 +- .../Localization/JavaScript/pt_BR.json | 4 +- .../Localization/JavaScript/ru.json | 4 +- .../Localization/LocalizationManager.cs | 4 +- .../Localization/Server/ar.json | 12 +- .../Localization/Server/ca.json | 12 +- .../Localization/Server/cs.json | 12 +- .../Localization/Server/da.json | 12 +- .../Localization/Server/de.json | 12 +- .../Localization/Server/el.json | 12 +- .../Localization/Server/en_GB.json | 12 +- .../Localization/Server/en_US.json | 12 +- .../Localization/Server/es.json | 4 +- .../Localization/Server/es_MX.json | 28 +- .../Localization/Server/fr.json | 8 +- .../Localization/Server/he.json | 12 +- .../Localization/Server/it.json | 4 +- .../Localization/Server/kk.json | 4 +- .../Localization/Server/ko.json | 12 +- .../Localization/Server/ms.json | 12 +- .../Localization/Server/nb.json | 12 +- .../Localization/Server/nl.json | 48 +-- .../Localization/Server/pl.json | 12 +- .../Localization/Server/pt_BR.json | 26 +- .../Localization/Server/pt_PT.json | 12 +- .../Localization/Server/ru.json | 18 +- .../Localization/Server/server.json | 24 +- .../Localization/Server/sv.json | 4 +- .../Localization/Server/vi.json | 12 +- .../Localization/Server/zh_TW.json | 12 +- .../MediaBrowser.Server.Implementations.csproj | 5 +- .../Sync/SyncManager.cs | 150 ++++++- .../Sync/SyncRepository.cs | 429 +++++++++++++++++++++ .../packages.config | 2 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 15 +- .../FFMpeg/FFMpegDownloader.cs | 2 +- MediaBrowser.Tests/Resolvers/MovieResolverTests.cs | 2 + MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 + .../MediaBrowser.WebDashboard.csproj | 12 + MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs | 2 +- .../Providers/BaseVideoNfoProvider.cs | 6 +- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 6 +- 97 files changed, 1537 insertions(+), 505 deletions(-) create mode 100644 MediaBrowser.Controller/Sync/ISyncRepository.cs create mode 100644 MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs create mode 100644 MediaBrowser.Model/Sync/SyncJobCreationResult.cs create mode 100644 MediaBrowser.Model/Sync/SyncJobItem.cs delete mode 100644 MediaBrowser.Model/Sync/SyncSchedule.cs delete mode 100644 MediaBrowser.Model/Sync/SyncScheduleQuery.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/SyncRepository.cs (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs index d407629644..99d2f144b1 100644 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ b/MediaBrowser.Api/Images/ImageByNameService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; @@ -100,13 +101,16 @@ namespace MediaBrowser.Api.Images /// private readonly IServerApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + /// /// Initializes a new instance of the class. /// /// The app paths. - public ImageByNameService(IServerApplicationPaths appPaths) + public ImageByNameService(IServerApplicationPaths appPaths, IFileSystem fileSystem) { _appPaths = appPaths; + _fileSystem = fileSystem; } public object Get(GetMediaInfoImages request) @@ -133,7 +137,7 @@ namespace MediaBrowser.Api.Images .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal)) .Select(i => new ImageByNameInfo { - Name = Path.GetFileNameWithoutExtension(i.FullName), + Name = _fileSystem.GetFileNameWithoutExtension(i), FileLength = i.Length, // For themeable images, use the Theme property diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs index be9f00a615..e21dc4a73b 100644 --- a/MediaBrowser.Api/Library/LibraryHelpers.cs +++ b/MediaBrowser.Api/Library/LibraryHelpers.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Library var rootFolderPath = appPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); - var shortcutFilename = Path.GetFileNameWithoutExtension(path); + var shortcutFilename = fileSystem.GetFileNameWithoutExtension(path); var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index 929d0a4639..e0dc07cdcf 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -15,13 +15,6 @@ namespace MediaBrowser.Api.Sync public string Id { get; set; } } - [Route("/Sync/Schedules/{Id}", "DELETE", Summary = "Cancels a sync job.")] - public class CancelSyncSchedule : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - [Route("/Sync/Jobs/{Id}", "GET", Summary = "Gets a sync job.")] public class GetSyncJob : IReturn { @@ -29,25 +22,26 @@ namespace MediaBrowser.Api.Sync public string Id { get; set; } } - [Route("/Sync/Schedules/{Id}", "GET", Summary = "Gets a sync job.")] - public class GetSyncSchedule : IReturn - { - [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - [Route("/Sync/Jobs", "GET", Summary = "Gets sync jobs.")] public class GetSyncJobs : IReturn> { - } - - [Route("/Sync/Schedules", "GET", Summary = "Gets sync schedules.")] - public class GetSyncSchedules : IReturn> - { + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } } [Route("/Sync/Jobs", "POST", Summary = "Gets sync jobs.")] - public class CreateSyncJob : SyncJobRequest + public class CreateSyncJob : SyncJobRequest, IReturn { } @@ -79,22 +73,13 @@ namespace MediaBrowser.Api.Sync { var result = _syncManager.GetJobs(new SyncJobQuery { - + StartIndex = request.StartIndex, + Limit = request.Limit }); return ToOptimizedResult(result); } - public object Get(GetSyncSchedules request) - { - var result = _syncManager.GetSchedules(new SyncScheduleQuery - { - - }); - - return ToOptimizedResult(result); - } - public object Get(GetSyncJob request) { var result = _syncManager.GetJob(request.Id); @@ -102,13 +87,6 @@ namespace MediaBrowser.Api.Sync return ToOptimizedResult(result); } - public object Get(GetSyncSchedule request) - { - var result = _syncManager.GetSchedule(request.Id); - - return ToOptimizedResult(result); - } - public void Delete(CancelSyncJob request) { var task = _syncManager.CancelJob(request.Id); @@ -116,18 +94,11 @@ namespace MediaBrowser.Api.Sync Task.WaitAll(task); } - public void Delete(CancelSyncSchedule request) + public object Post(CreateSyncJob request) { - var task = _syncManager.CancelSchedule(request.Id); + var result = _syncManager.CreateJob(request); - Task.WaitAll(task); - } - - public void Post(CreateSyncJob request) - { - var task = _syncManager.CreateJob(request); - - Task.WaitAll(task); + return ToOptimizedResult(result); } } } diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs index dfe93c5c93..2d67ec9756 100644 --- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -367,5 +367,20 @@ namespace MediaBrowser.Common.Implementations.IO return newPath; } + + public string GetFileNameWithoutExtension(FileSystemInfo info) + { + if (info is DirectoryInfo) + { + return info.Name; + } + + return Path.GetFileNameWithoutExtension(info.FullName); + } + + public string GetFileNameWithoutExtension(string path) + { + return Path.GetFileNameWithoutExtension(path); + } } } diff --git a/MediaBrowser.Common/IO/IFileSystem.cs b/MediaBrowser.Common/IO/IFileSystem.cs index de9b7e88ac..27307a0e9d 100644 --- a/MediaBrowser.Common/IO/IFileSystem.cs +++ b/MediaBrowser.Common/IO/IFileSystem.cs @@ -112,5 +112,19 @@ namespace MediaBrowser.Common.IO /// To. /// System.String. string SubstitutePath(string path, string from, string to); + + /// + /// Gets the file name without extension. + /// + /// The information. + /// System.String. + string GetFileNameWithoutExtension(FileSystemInfo info); + + /// + /// Gets the file name without extension. + /// + /// The path. + /// System.String. + string GetFileNameWithoutExtension(string path); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b891bcfb2a..2bdbab0848 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -529,10 +529,10 @@ namespace MediaBrowser.Controller.Entities .Where(i => string.Equals(i.Name, TrailerFolderName, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) .ToList(); - + // Support plex/xbmc convention files.AddRange(fileSystemChildren.OfType() - .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) + .Where(i => FileSystem.GetFileNameWithoutExtension(i).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) ); return LibraryManager.ResolvePaths(files, directoryService, null).Select(video => @@ -564,7 +564,7 @@ namespace MediaBrowser.Controller.Entities // Support plex/xbmc convention files.AddRange(fileSystemChildren.OfType() - .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) ); return LibraryManager.ResolvePaths(files, directoryService, null).Select(audio => @@ -1564,7 +1564,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path)) { - Name = System.IO.Path.GetFileNameWithoutExtension(Path); + Name = FileSystem.GetFileNameWithoutExtension(Path); hasChanges = true; } diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index af0ff8319e..86699272e8 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using System; @@ -189,11 +190,14 @@ namespace MediaBrowser.Controller.Library /// /// The path. /// The directory service. + /// The file system. /// true if [is season folder] [the specified path]; otherwise, false. - private static bool IsSeasonFolder(string path, IDirectoryService directoryService) + private static bool IsSeasonFolder(string path, IDirectoryService directoryService, IFileSystem fileSystem) { // It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3 - return GetSeasonNumberFromPath(path) != null && !directoryService.GetFiles(path).Any(i => EntityResolutionHelper.IsAudioFile(i.FullName) && !string.Equals(Path.GetFileNameWithoutExtension(i.FullName), BaseItem.ThemeSongFilename)); + return GetSeasonNumberFromPath(path) != null && + !directoryService.GetFiles(path) + .Any(i => EntityResolutionHelper.IsAudioFile(i.FullName) && !string.Equals(fileSystem.GetFileNameWithoutExtension(i), BaseItem.ThemeSongFilename)); } /// @@ -204,7 +208,7 @@ namespace MediaBrowser.Controller.Library /// The file system children. /// The directory service. /// true if [is series folder] [the specified path]; otherwise, false. - public static bool IsSeriesFolder(string path, bool considerSeasonlessSeries, IEnumerable fileSystemChildren, IDirectoryService directoryService) + public static bool IsSeriesFolder(string path, bool considerSeasonlessSeries, IEnumerable fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem) { // A folder with more than 3 non-season folders in will not becounted as a series var nonSeriesFolders = 0; @@ -225,7 +229,7 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { - if (IsSeasonFolder(child.FullName, directoryService)) + if (IsSeasonFolder(child.FullName, directoryService, fileSystem)) { return true; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5c3f10b54f..aee118f9ac 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -322,6 +322,7 @@ + diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index 714e71a1f8..1d5ab7d3e7 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Sync /// /// The request. /// Task. - Task> CreateJob(SyncJobRequest request); + Task CreateJob(SyncJobRequest request); /// /// Gets the jobs. @@ -21,25 +21,12 @@ namespace MediaBrowser.Controller.Sync /// QueryResult<SyncJob>. QueryResult GetJobs(SyncJobQuery query); - /// - /// Gets the schedules. - /// - /// QueryResult<SyncSchedule>. - QueryResult GetSchedules(SyncScheduleQuery query); - /// /// Gets the job. /// /// The identifier. /// SyncJob. SyncJob GetJob(string id); - - /// - /// Gets the schedule. - /// - /// The identifier. - /// SyncSchedule. - SyncSchedule GetSchedule(string id); /// /// Cancels the job. @@ -48,13 +35,6 @@ namespace MediaBrowser.Controller.Sync /// Task. Task CancelJob(string id); - /// - /// Cancels the schedule. - /// - /// The identifier. - /// Task. - Task CancelSchedule(string id); - /// /// Adds the parts. /// diff --git a/MediaBrowser.Controller/Sync/ISyncRepository.cs b/MediaBrowser.Controller/Sync/ISyncRepository.cs new file mode 100644 index 0000000000..9cce69bdc2 --- /dev/null +++ b/MediaBrowser.Controller/Sync/ISyncRepository.cs @@ -0,0 +1,58 @@ +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Sync; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Sync +{ + public interface ISyncRepository + { + /// + /// Gets the job. + /// + /// The identifier. + /// SyncJob. + SyncJob GetJob(string id); + + /// + /// Creates the specified job. + /// + /// The job. + /// Task. + Task Create(SyncJob job); + + /// + /// Updates the specified job. + /// + /// The job. + /// Task. + Task Update(SyncJob job); + + /// + /// Gets the jobs. + /// + /// The query. + /// QueryResult<SyncJob>. + QueryResult GetJobs(SyncJobQuery query); + + /// + /// Gets the job item. + /// + /// The identifier. + /// SyncJobItem. + SyncJobItem GetJobItem(string id); + + /// + /// Creates the specified job item. + /// + /// The job item. + /// Task. + Task Create(SyncJobItem jobItem); + + /// + /// Updates the specified job item. + /// + /// The job item. + /// Task. + Task Update(SyncJobItem jobItem); + } +} diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index 230b5e145e..fc5fd4061e 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -1,7 +1,9 @@ -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Providers; +using MediaBrowser.Dlna.ContentDirectory; using MediaBrowser.Dlna.PlayTo; using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Channels; @@ -20,7 +22,7 @@ namespace MediaBrowser.Dlna.Channels private readonly IServerConfigurationManager _config; private readonly ILogger _logger; private readonly IHttpClient _httpClient; - + private DeviceDiscovery _deviceDiscovery; private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1); @@ -96,7 +98,7 @@ namespace MediaBrowser.Dlna.Channels } catch (Exception ex) { - + } finally { @@ -123,7 +125,7 @@ namespace MediaBrowser.Dlna.Channels } await _syncLock.WaitAsync().ConfigureAwait(false); - + try { var list = _servers.ToList(); @@ -163,7 +165,23 @@ namespace MediaBrowser.Dlna.Channels public IEnumerable GetChannels() { - return _servers.Select(i => new ServerChannel(i)).ToList(); + //if (_servers.Count > 0) + //{ + // var service = _servers[0].Properties.Services + // .FirstOrDefault(i => string.Equals(i.ServiceType, "urn:schemas-upnp-org:service:ContentDirectory:1", StringComparison.OrdinalIgnoreCase)); + + // var controlUrl = service == null ? null : (_servers[0].Properties.BaseUrl.TrimEnd('/') + "/" + service.ControlUrl.TrimStart('/')); + + // if (!string.IsNullOrEmpty(controlUrl)) + // { + // return new List + // { + // new ServerChannel(_servers.ToList(), _httpClient, _logger, controlUrl) + // }; + // } + //} + + return new List(); } public void Dispose() @@ -178,31 +196,37 @@ namespace MediaBrowser.Dlna.Channels public class ServerChannel : IChannel, IFactoryChannel { - private readonly Device _device; + private readonly List _servers = new List(); + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _controlUrl; - public ServerChannel(Device device) + public ServerChannel(List servers, IHttpClient httpClient, ILogger logger, string controlUrl) { - _device = device; + _servers = servers; + _httpClient = httpClient; + _logger = logger; + _controlUrl = controlUrl; } public string Name { - get { return _device.Properties.Name; } + get { return "Devices"; } } public string Description { - get { return _device.Properties.ModelDescription; } + get { return string.Empty; } } public string DataVersion { - get { return "1"; } + get { return DateTime.UtcNow.Ticks.ToString(); } } public string HomePageUrl { - get { return _device.Properties.ModelUrl; } + get { return string.Empty; } } public ChannelParentalRating ParentalRating @@ -234,10 +258,68 @@ namespace MediaBrowser.Dlna.Channels return true; } - public Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) + public async Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) { - // TODO: Implement - return Task.FromResult(new ChannelItemResult()); + IEnumerable items; + + if (string.IsNullOrWhiteSpace(query.FolderId)) + { + items = _servers.Select(i => new ChannelItemInfo + { + FolderType = ChannelFolderType.Container, + Id = GetServerId(i), + Name = i.Properties.Name, + Overview = i.Properties.ModelDescription, + Type = ChannelItemType.Folder + }); + } + else + { + var idParts = query.FolderId.Split('|'); + var folderId = idParts.Length == 2 ? idParts[1] : null; + + var result = await new ContentDirectoryBrowser(_httpClient, _logger).Browse(new ContentDirectoryBrowseRequest + { + Limit = query.Limit, + StartIndex = query.StartIndex, + ParentId = folderId, + ContentDirectoryUrl = _controlUrl + + }, cancellationToken).ConfigureAwait(false); + + items = result.Items.ToList(); + } + + var list = items.ToList(); + var count = list.Count; + + list = ApplyPaging(list, query).ToList(); + + return new ChannelItemResult + { + Items = list, + TotalRecordCount = count + }; + } + + private string GetServerId(Device device) + { + return device.Properties.UUID.GetMD5().ToString("N"); + } + + private IEnumerable ApplyPaging(IEnumerable items, InternalChannelItemQuery query) + { + if (query.StartIndex.HasValue) + { + items = items.Skip(query.StartIndex.Value); + } + + if (query.Limit.HasValue) + { + items = items.Take(query.Limit.Value); + } + + return items; } public Task GetChannelImage(ImageType type, CancellationToken cancellationToken) diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs new file mode 100644 index 0000000000..0c62ada809 --- /dev/null +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs @@ -0,0 +1,126 @@ +using System.Linq; +using System.Xml.Linq; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Dlna.PlayTo; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Dlna.ContentDirectory +{ + public class ContentDirectoryBrowser + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + + public ContentDirectoryBrowser(IHttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + private static XNamespace UNamespace = "u"; + + public async Task> Browse(ContentDirectoryBrowseRequest request, CancellationToken cancellationToken) + { + var options = new HttpRequestOptions + { + CancellationToken = cancellationToken, + UserAgent = "Media Browser", + RequestContentType = "text/xml; charset=\"utf-8\"", + LogErrorResponseBody = true, + Url = request.ContentDirectoryUrl + }; + + options.RequestHeaders["SOAPACTION"] = "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"; + + options.RequestContent = GetRequestBody(request); + + var response = await _httpClient.SendAsync(options, "POST"); + + using (var reader = new StreamReader(response.Content)) + { + var doc = XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); + + var queryResult = new QueryResult(); + + if (doc.Document == null) + return queryResult; + + var responseElement = doc.Document.Descendants(UNamespace + "BrowseResponse").ToList(); + + var countElement = responseElement.Select(i => i.Element("TotalMatches")).FirstOrDefault(i => i != null); + var countValue = countElement == null ? null : countElement.Value; + + int count; + if (!string.IsNullOrWhiteSpace(countValue) && int.TryParse(countValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out count)) + { + queryResult.TotalRecordCount = count; + + var resultElement = responseElement.Select(i => i.Element("Result")).FirstOrDefault(i => i != null); + var resultString = (string)resultElement; + + if (resultElement != null) + { + var xElement = XElement.Parse(resultString); + } + } + + return queryResult; + } + } + + private string GetRequestBody(ContentDirectoryBrowseRequest request) + { + var builder = new StringBuilder(); + + builder.Append(""); + + builder.Append(""); + builder.Append(""); + + if (string.IsNullOrWhiteSpace(request.ParentId)) + { + request.ParentId = "1"; + } + + builder.AppendFormat("{0}", SecurityElement.Escape(request.ParentId)); + builder.Append("BrowseDirectChildren"); + + //builder.Append("BrowseMetadata"); + + builder.Append("*"); + + request.StartIndex = request.StartIndex ?? 0; + builder.AppendFormat("{0}", SecurityElement.Escape(request.StartIndex.Value.ToString(CultureInfo.InvariantCulture))); + + request.Limit = request.Limit ?? 20; + if (request.Limit.HasValue) + { + builder.AppendFormat("{0}", SecurityElement.Escape(request.Limit.Value.ToString(CultureInfo.InvariantCulture))); + } + + builder.Append(""); + + builder.Append(""); + builder.Append(""); + + return builder.ToString(); + } + } + + public class ContentDirectoryBrowseRequest + { + public int? StartIndex { get; set; } + public int? Limit { get; set; } + public string ParentId { get; set; } + public string ContentDirectoryUrl { get; set; } + } +} diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 333521bb57..29bd21407b 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -372,7 +372,7 @@ namespace MediaBrowser.Dlna Info = new DeviceProfileInfo { Id = i.FullName.ToLower().GetMD5().ToString("N"), - Name = Path.GetFileNameWithoutExtension(i.FullName), + Name = _fileSystem.GetFileNameWithoutExtension(i), Type = type } }) diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index ec85277384..461470b7ac 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -57,6 +57,7 @@ + diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 0d7c450d61..85e18150ed 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -819,12 +819,12 @@ namespace MediaBrowser.Dlna.PlayTo foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList"))) { if (services == null) - return null; + continue; var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); if (servicesList == null) - return null; + continue; foreach (var element in servicesList) { diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs index 29fd76aa56..ccb2acd1d6 100644 --- a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs @@ -1,11 +1,19 @@ -using System.Collections.Generic; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; +using System.Collections.Generic; namespace MediaBrowser.LocalMetadata.Images { public class CollectionFolderLocalImageProvider : ILocalImageFileProvider, IHasOrder { + private readonly IFileSystem _fileSystem; + + public CollectionFolderLocalImageProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + public string Name { get { return "Collection Folder Images"; } @@ -29,7 +37,7 @@ namespace MediaBrowser.LocalMetadata.Images { var collectionFolder = (CollectionFolder)item; - return new LocalImageProvider().GetImages(item, collectionFolder.PhysicalLocations, directoryService); + return new LocalImageProvider(_fileSystem).GetImages(item, collectionFolder.PhysicalLocations, directoryService); } } } diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index f40020c84e..cd9b78201e 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -11,6 +12,13 @@ namespace MediaBrowser.LocalMetadata.Images { public class EpisodeLocalLocalImageProvider : ILocalImageFileProvider { + private readonly IFileSystem _fileSystem; + + public EpisodeLocalLocalImageProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + public string Name { get { return "Local Images"; } @@ -27,7 +35,7 @@ namespace MediaBrowser.LocalMetadata.Images var parentPathFiles = directoryService.GetFileSystemEntries(parentPath); - var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path); + var nameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(item.Path); var files = GetFilesFromParentFolder(nameWithoutExtension, parentPathFiles); @@ -60,7 +68,7 @@ namespace MediaBrowser.LocalMetadata.Images if (BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)) { - var currentNameWithoutExtension = Path.GetFileNameWithoutExtension(i.Name); + var currentNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(i); if (string.Equals(filenameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs b/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs index 3f84df462d..d992e20262 100644 --- a/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.LocalMetadata.Images try { - return new LocalImageProvider().GetImages(item, path, directoryService); + return new LocalImageProvider(_fileSystem).GetImages(item, path, directoryService); } catch (DirectoryNotFoundException) { diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index 8c4f6247cb..c126af884b 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -1,19 +1,22 @@ -using System.Collections.Generic; -using System.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; +using System.Collections.Generic; +using System.IO; namespace MediaBrowser.LocalMetadata.Images { public class InternalMetadataFolderImageProvider : ILocalImageFileProvider, IHasOrder { private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; - public InternalMetadataFolderImageProvider(IServerConfigurationManager config) + public InternalMetadataFolderImageProvider(IServerConfigurationManager config, IFileSystem fileSystem) { _config = config; + _fileSystem = fileSystem; } public string Name @@ -57,7 +60,7 @@ namespace MediaBrowser.LocalMetadata.Images try { - return new LocalImageProvider().GetImages(item, path, directoryService); + return new LocalImageProvider(_fileSystem).GetImages(item, path, directoryService); } catch (DirectoryNotFoundException) { diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 5d77628231..f69c261d8a 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -1,19 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; namespace MediaBrowser.LocalMetadata.Images { public class LocalImageProvider : ILocalImageFileProvider { + private readonly IFileSystem _fileSystem; + + public LocalImageProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + public string Name { get { return "Local Images"; } @@ -117,7 +125,7 @@ namespace MediaBrowser.LocalMetadata.Images var baseItem = item as BaseItem; if (baseItem != null && baseItem.IsInMixedFolder) { - imagePrefix = Path.GetFileNameWithoutExtension(item.Path) + "-"; + imagePrefix = _fileSystem.GetFileNameWithoutExtension(item.Path) + "-"; } PopulatePrimaryImages(item, images, files, imagePrefix); @@ -172,7 +180,7 @@ namespace MediaBrowser.LocalMetadata.Images if (!string.IsNullOrEmpty(item.Path)) { - var name = Path.GetFileNameWithoutExtension(item.Path); + var name = _fileSystem.GetFileNameWithoutExtension(item.Path); if (!string.IsNullOrEmpty(name)) { @@ -188,7 +196,7 @@ namespace MediaBrowser.LocalMetadata.Images if (!string.IsNullOrEmpty(item.Path)) { - var name = Path.GetFileNameWithoutExtension(item.Path); + var name = _fileSystem.GetFileNameWithoutExtension(item.Path); if (!string.IsNullOrEmpty(name)) { @@ -259,6 +267,7 @@ namespace MediaBrowser.LocalMetadata.Images } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private void PopulateSeasonImagesFromSeriesFolder(Season season, List images, IDirectoryService directoryService) { var seasonNumber = season.IndexNumber; @@ -316,7 +325,7 @@ namespace MediaBrowser.LocalMetadata.Images private FileSystemInfo GetImage(IEnumerable files, string name) { var candidates = files - .Where(i => string.Equals(name, Path.GetFileNameWithoutExtension(i.Name), StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase)) .ToList(); return BaseItem.SupportedImageExtensions diff --git a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs index 681706321b..83ef6e4243 100644 --- a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Providers var directoryPath = directoryInfo.FullName; - var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); + var specificFile = Path.Combine(directoryPath, FileSystem.GetFileNameWithoutExtension(info.Path) + ".xml"); var file = new FileInfo(specificFile); diff --git a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs index 6ba1912a5d..fb3a01bf26 100644 --- a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.LocalMetadata.Parsers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using System.Collections.Generic; +using System.IO; +using System.Threading; namespace MediaBrowser.LocalMetadata.Providers { @@ -47,7 +47,7 @@ namespace MediaBrowser.LocalMetadata.Providers var directoryPath = directoryInfo.FullName; - var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); + var specificFile = Path.Combine(directoryPath, fileSystem.GetFileNameWithoutExtension(info.Path) + ".xml"); var file = new FileInfo(specificFile); diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index a892f8bf16..c397cdcd52 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -848,6 +848,12 @@ Sync\SyncJob.cs + + Sync\SyncJobCreationResult.cs + + + Sync\SyncJobItem.cs + Sync\SyncJobQuery.cs @@ -860,12 +866,6 @@ Sync\SyncQuality.cs - - Sync\SyncSchedule.cs - - - Sync\SyncScheduleQuery.cs - Sync\SyncTarget.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index ab2a99109e..1b74fcf51c 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -805,6 +805,12 @@ Sync\SyncJob.cs + + Sync\SyncJobCreationResult.cs + + + Sync\SyncJobItem.cs + Sync\SyncJobQuery.cs @@ -817,12 +823,6 @@ Sync\SyncQuality.cs - - Sync\SyncSchedule.cs - - - Sync\SyncScheduleQuery.cs - Sync\SyncTarget.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 6cdd2b8f5e..fd38ff7239 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -297,12 +297,12 @@ + + - - diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 74dd79497c..f69fccae50 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -1,4 +1,6 @@ - +using System; +using System.Collections.Generic; + namespace MediaBrowser.Model.Sync { public class SyncJob @@ -14,39 +16,79 @@ namespace MediaBrowser.Model.Sync /// The device identifier. public string TargetId { get; set; } /// - /// Gets or sets the item identifier. - /// - /// The item identifier. - public string ItemId { get; set; } - /// /// Gets or sets the quality. /// /// The quality. public SyncQuality Quality { get; set; } /// + /// Gets or sets the current progress. + /// + /// The current progress. + public double? Progress { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// /// Gets or sets the status. /// /// The status. - public SyncJobStatus Status { get; set; } + public SyncJobStatus Status { get; set; } /// - /// Gets or sets the current progress. + /// Gets or sets the user identifier. /// - /// The current progress. - public double? CurrentProgress { get; set; } + /// The user identifier. + public string UserId { get; set; } /// - /// Gets or sets the synchronize rule identifier. + /// Gets or sets a value indicating whether [unwatched only]. /// - /// The synchronize rule identifier. - public string SyncScheduleId { get; set; } + /// true if [unwatched only]; otherwise, false. + public bool UnwatchedOnly { get; set; } /// - /// Gets or sets the transcoded path. + /// Gets or sets the limit. /// - /// The transcoded path. - public string TranscodedPath { get; set; } + /// The limit. + public long? Limit { get; set; } /// - /// Gets or sets the name. + /// Gets or sets the type of the limit. /// - /// The name. - public string Name { get; set; } + /// The type of the limit. + public SyncLimitType? LimitType { get; set; } + /// + /// Gets or sets the requested item ids. + /// + /// The requested item ids. + public List RequestedItemIds { get; set; } + /// + /// Gets or sets a value indicating whether this instance is dynamic. + /// + /// true if this instance is dynamic; otherwise, false. + public bool IsDynamic { get; set; } + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime DateLastModified { get; set; } + /// + /// Gets or sets the item count. + /// + /// The item count. + public int ItemCount { get; set; } + + public string ParentName { get; set; } + public string PrimaryImageItemId { get; set; } + public string PrimaryImageTag { get; set; } + public double? PrimaryImageAspectRatio { get; set; } + + public SyncJob() + { + RequestedItemIds = new List(); + } } } diff --git a/MediaBrowser.Model/Sync/SyncJobCreationResult.cs b/MediaBrowser.Model/Sync/SyncJobCreationResult.cs new file mode 100644 index 0000000000..797318b2a9 --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncJobCreationResult.cs @@ -0,0 +1,8 @@ + +namespace MediaBrowser.Model.Sync +{ + public class SyncJobCreationResult + { + public SyncJob Job { get; set; } + } +} diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs new file mode 100644 index 0000000000..141546eb5a --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncJobItem.cs @@ -0,0 +1,48 @@ + +namespace MediaBrowser.Model.Sync +{ + public class SyncJobItem + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the job identifier. + /// + /// The job identifier. + public string JobId { get; set; } + + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public string ItemId { get; set; } + + /// + /// Gets or sets the target identifier. + /// + /// The target identifier. + public string TargetId { get; set; } + + /// + /// Gets or sets the output path. + /// + /// The output path. + public string OutputPath { get; set; } + + /// + /// Gets or sets the status. + /// + /// The status. + public SyncJobStatus Status { get; set; } + + /// + /// Gets or sets the current progress. + /// + /// The current progress. + public double? CurrentProgress { get; set; } + } +} diff --git a/MediaBrowser.Model/Sync/SyncJobQuery.cs b/MediaBrowser.Model/Sync/SyncJobQuery.cs index f41544db9a..74b35186e0 100644 --- a/MediaBrowser.Model/Sync/SyncJobQuery.cs +++ b/MediaBrowser.Model/Sync/SyncJobQuery.cs @@ -3,5 +3,15 @@ namespace MediaBrowser.Model.Sync { public class SyncJobQuery { + /// + /// Gets or sets the start index. + /// + /// The start index. + public int? StartIndex { get; set; } + /// + /// Gets or sets the limit. + /// + /// The limit. + public int? Limit { get; set; } } } diff --git a/MediaBrowser.Model/Sync/SyncJobRequest.cs b/MediaBrowser.Model/Sync/SyncJobRequest.cs index cd833068ee..7728aad9bd 100644 --- a/MediaBrowser.Model/Sync/SyncJobRequest.cs +++ b/MediaBrowser.Model/Sync/SyncJobRequest.cs @@ -5,10 +5,10 @@ namespace MediaBrowser.Model.Sync public class SyncJobRequest { /// - /// Gets or sets the device identifier. + /// Gets or sets the target identifier. /// - /// The device identifier. - public List TargetIds { get; set; } + /// The target identifier. + public string TargetId { get; set; } /// /// Gets or sets the item ids. /// @@ -24,11 +24,35 @@ namespace MediaBrowser.Model.Sync /// /// The name. public string Name { get; set; } + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + /// + /// Gets or sets a value indicating whether [unwatched only]. + /// + /// true if [unwatched only]; otherwise, false. + public bool UnwatchedOnly { get; set; } + /// + /// Gets or sets the limit. + /// + /// The limit. + public long? Limit { get; set; } + /// + /// Gets or sets the type of the limit. + /// + /// The type of the limit. + public SyncLimitType? LimitType { get; set; } public SyncJobRequest() { - TargetIds = new List(); ItemIds = new List(); } } + + public enum SyncLimitType + { + ItemCount = 0 + } } diff --git a/MediaBrowser.Model/Sync/SyncJobStatus.cs b/MediaBrowser.Model/Sync/SyncJobStatus.cs index 2a216fe044..ef4d8d7bfc 100644 --- a/MediaBrowser.Model/Sync/SyncJobStatus.cs +++ b/MediaBrowser.Model/Sync/SyncJobStatus.cs @@ -3,33 +3,11 @@ namespace MediaBrowser.Model.Sync { public enum SyncJobStatus { - /// - /// The queued - /// Queued = 0, - /// - /// The transcoding - /// Transcoding = 1, - /// - /// The transcoding failed - /// TranscodingFailed = 2, - /// - /// The transcoding completed - /// - TranscodingCompleted = 3, - /// - /// The transfering - /// - Transfering = 4, - /// - /// The transfer failed - /// - TransferFailed = 4, - /// - /// The completed - /// - Completed = 6 + Transfering = 3, + Completed = 4, + Cancelled = 5 } } diff --git a/MediaBrowser.Model/Sync/SyncSchedule.cs b/MediaBrowser.Model/Sync/SyncSchedule.cs deleted file mode 100644 index 297cbd1450..0000000000 --- a/MediaBrowser.Model/Sync/SyncSchedule.cs +++ /dev/null @@ -1,12 +0,0 @@ - -namespace MediaBrowser.Model.Sync -{ - public class SyncSchedule - { - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Model/Sync/SyncScheduleQuery.cs b/MediaBrowser.Model/Sync/SyncScheduleQuery.cs deleted file mode 100644 index b704a358ca..0000000000 --- a/MediaBrowser.Model/Sync/SyncScheduleQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace MediaBrowser.Model.Sync -{ - public class SyncScheduleQuery - { - } -} diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index cf7904e5cb..e17a96a43e 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -216,12 +216,6 @@ namespace MediaBrowser.Providers.BoxSets { mainResult = _json.DeserializeFromStream(json); } - - if (String.IsNullOrEmpty(mainResult.overview)) - { - _logger.Error("Unable to find information for (id:" + id + ")"); - return null; - } } } return mainResult; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 1b2e9fa6d6..d7110c1ec5 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -292,7 +292,7 @@ namespace MediaBrowser.Providers.Manager private string GetStandardSavePath(IHasImages item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) { string filename; - + switch (type) { case ImageType.Art: @@ -305,7 +305,7 @@ namespace MediaBrowser.Providers.Manager filename = item is MusicAlbum ? "cdart" : "disc"; break; case ImageType.Primary: - filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder"; + filename = item is Episode ? _fileSystem.GetFileNameWithoutExtension(item.Path) : "folder"; break; case ImageType.Backdrop: filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex); @@ -367,7 +367,7 @@ namespace MediaBrowser.Providers.Manager return zeroIndexFilename; } - var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); + var filenames = images.Select(i => _fileSystem.GetFileNameWithoutExtension(i.Path)).ToList(); var current = 1; while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) @@ -473,7 +473,7 @@ namespace MediaBrowser.Providers.Manager { var seasonFolder = Path.GetDirectoryName(item.Path); - var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; + var imageFilename = _fileSystem.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; return new[] { Path.Combine(seasonFolder, imageFilename) }; } @@ -560,7 +560,7 @@ namespace MediaBrowser.Providers.Manager } var folder = Path.GetDirectoryName(item.Path); - return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); + return Path.Combine(folder, _fileSystem.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 9e9936cab4..33b69d71a9 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -175,7 +175,7 @@ namespace MediaBrowser.Providers.MediaInfo if (video != null && !video.IsPlaceHolder) { return !video.SubtitleFiles - .SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, false) + .SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, _fileSystem, 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 c0313561ae..a2e1ba05a7 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -477,7 +477,7 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options, CancellationToken cancellationToken) { - var subtitleResolver = new SubtitleResolver(_localization); + var subtitleResolver = new SubtitleResolver(_localization, _fileSystem); var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, false).ToList(); @@ -790,7 +790,7 @@ namespace MediaBrowser.Providers.MediaInfo // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file if (files.Count > 0) { - var parts = Path.GetFileNameWithoutExtension(files[0].FullName).Split('_'); + var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_'); if (parts.Length == 3) { @@ -798,7 +798,7 @@ namespace MediaBrowser.Providers.MediaInfo files = files.TakeWhile(f => { - var fileParts = Path.GetFileNameWithoutExtension(f.FullName).Split('_'); + var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index c12f74fa61..78d1fac479 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; @@ -13,10 +14,12 @@ namespace MediaBrowser.Providers.MediaInfo public class SubtitleResolver { private readonly ILocalizationManager _localization; + private readonly IFileSystem _fileSystem; - public SubtitleResolver(ILocalizationManager localization) + public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem) { _localization = localization; + _fileSystem = fileSystem; } public IEnumerable GetExternalSubtitleStreams(Video video, @@ -24,17 +27,17 @@ namespace MediaBrowser.Providers.MediaInfo IDirectoryService directoryService, bool clearCache) { - var files = GetSubtitleFiles(video, directoryService, clearCache); + var files = GetSubtitleFiles(video, directoryService, _fileSystem, clearCache); var streams = new List(); - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + var videoFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(video.Path); foreach (var file in files) { var fullName = file.FullName; - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + var fileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(file); var codec = Path.GetExtension(fullName).ToLower().TrimStart('.'); @@ -96,7 +99,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - public static IEnumerable GetSubtitleFiles(Video video, IDirectoryService directoryService, bool clearCache) + public static IEnumerable GetSubtitleFiles(Video video, IDirectoryService directoryService, IFileSystem fileSystem, bool clearCache) { var containingPath = video.ContainingFolderPath; @@ -107,16 +110,14 @@ namespace MediaBrowser.Providers.MediaInfo var files = directoryService.GetFiles(containingPath, clearCache); - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + var videoFileNameWithoutExtension = fileSystem.GetFileNameWithoutExtension(video.Path); return files.Where(i => { if (!i.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)) { - var fullName = i.FullName; - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + var fileNameWithoutExtension = fileSystem.GetFileNameWithoutExtension(i); if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 65fe32a991..32d53db436 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -252,7 +252,7 @@ namespace MediaBrowser.Providers.Movies var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId); var filename = string.Format("all-{0}.json", - preferredLanguage ?? string.Empty); + preferredLanguage); return Path.Combine(path, filename); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 08499a20ba..34656f05bc 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.Subtitles using (var stream = response.Stream) { var savePath = Path.Combine(Path.GetDirectoryName(video.Path), - Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower()); + _fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower()); if (response.IsForced) { diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index d448118860..594ba9d235 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -248,12 +248,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .ToList(); var folder = Path.GetDirectoryName(targetPath); - var targetFileNameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath); - + var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath); + try { var filesOfOtherExtensions = Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly) - .Where(i => EntityResolutionHelper.IsVideoFile(i) && string.Equals(Path.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); + .Where(i => EntityResolutionHelper.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); episodePaths.AddRange(filesOfOtherExtensions); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index d0ea070563..6e0b654fd9 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,5 +1,4 @@ -using Amib.Threading; -using Funq; +using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -15,7 +14,6 @@ using ServiceStack.Host.HttpListener; using ServiceStack.Logging; using ServiceStack.Web; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -37,7 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer private IHttpListener _listener; - private readonly SmartThreadPool _threadPoolManager; private const int IdleTimeout = 300; private readonly ContainerAdapter _containerAdapter; @@ -62,9 +59,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer _logger = logManager.GetLogger("HttpServer"); _containerAdapter = new ContainerAdapter(applicationHost); - - _threadPoolManager = new SmartThreadPool(IdleTimeout, - maxWorkerThreads: Math.Max(16, Environment.ProcessorCount * 2)); } public override void Configure(Container container) @@ -158,8 +152,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); _listener = NativeWebSocket.IsSupported - ? _listener = new HttpListenerServer(_logger, _threadPoolManager) - : _listener = new WebSocketSharpListener(_logger, _threadPoolManager); + ? _listener = new HttpListenerServer(_logger) + : _listener = new WebSocketSharpListener(_logger); _listener.WebSocketHandler = WebSocketHandler; _listener.ErrorHandler = ErrorHandler; @@ -356,8 +350,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (disposing) { - _threadPoolManager.Dispose(); - Stop(); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs index 7f766129e7..12106c32e0 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs @@ -1,6 +1,4 @@ -using System.Text; -using Amib.Threading; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; using ServiceStack; using ServiceStack.Host.HttpListener; @@ -10,6 +8,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,7 +19,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener private readonly ILogger _logger; private HttpListener _listener; private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); - private readonly SmartThreadPool _threadPoolManager; public System.Action ErrorHandler { get; set; } public Action WebSocketHandler { get; set; } @@ -28,11 +26,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public HttpListenerServer(ILogger logger, SmartThreadPool threadPoolManager) + public HttpListenerServer(ILogger logger) { _logger = logger; - - _threadPoolManager = threadPoolManager; } /// @@ -63,7 +59,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener _listener.Start(); - ThreadPool.QueueUserWorkItem(Listen); + Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning); } private bool IsListening @@ -72,7 +68,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener } // Loop here to begin processing of new requests. - private void Listen(object state) + private void Listen() { while (IsListening) { @@ -130,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener _listenForNextRequest.Set(); } - _threadPoolManager.QueueWorkItem(() => InitTask(context)); + Task.Factory.StartNew(() => InitTask(context)); } private void InitTask(HttpListenerContext context) @@ -287,8 +283,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener if (disposing) { - _threadPoolManager.Dispose(); - Stop(); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index cf756d9f27..b18d0df5eb 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -1,14 +1,13 @@ -using System; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using ServiceStack; +using ServiceStack.Web; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Amib.Threading; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; -using ServiceStack; -using ServiceStack.Web; using WebSocketSharp.Net; using WebSocketSharp.Server; @@ -20,12 +19,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private WebSocketSharp.Server.HttpServer _httpsv; private readonly ILogger _logger; - private readonly SmartThreadPool _threadPoolManager; - public WebSocketSharpListener(ILogger logger, SmartThreadPool threadPoolManager) + public WebSocketSharpListener(ILogger logger) { _logger = logger; - _threadPoolManager = threadPoolManager; } public IEnumerable LocalEndPoints @@ -33,9 +30,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return _localEndPoints.Keys.ToList(); } } - public System.Action ErrorHandler { get; set; } + public Action ErrorHandler { get; set; } - public System.Func RequestHandler { get; set; } + public Func RequestHandler { get; set; } public Action WebSocketHandler { get; set; } @@ -50,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp void _httpsv_OnRequest(object sender, HttpRequestEventArgs e) { - _threadPoolManager.QueueWorkItem(() => InitTask(e.Context)); + Task.Factory.StartNew(() => InitTask(e.Context)); } private void InitTask(HttpListenerContext context) diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 8229c8a2f3..7b58dd7c48 100644 --- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.Library if (args.Parent != null) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(filename)) + if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(filename)) { return true; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 64e0a6662f..8310895e67 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1157,7 +1157,7 @@ namespace MediaBrowser.Server.Implementations.Library private string GetCollectionType(string path) { return new DirectoryInfo(path).EnumerateFiles("*.collection", SearchOption.TopDirectoryOnly) - .Select(i => Path.GetFileNameWithoutExtension(i.FullName)) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); } @@ -1486,23 +1486,22 @@ namespace MediaBrowser.Server.Implementations.Library public async Task GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken) { - var id = "namedview_3_" + name; - var guid = id.GetMD5(); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, + "views", + _fileSystem.GetValidFilename(type)); - var item = GetItemById(guid) as UserView; + var id = (path + "_namedview_" + name).GetMBId(typeof(UserView)); + + var item = GetItemById(id) as UserView; if (item == null) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, - "views", - _fileSystem.GetValidFilename(type)); - Directory.CreateDirectory(Path.GetDirectoryName(path)); item = new UserView { Path = path, - Id = guid, + Id = id, DateCreated = DateTime.UtcNow, Name = name, ViewType = type, diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 64d2db2e45..2ee3d49a44 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.IO; @@ -17,6 +19,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// public class MusicAlbumResolver : ItemResolver { + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + /// /// Gets the priority. /// @@ -64,10 +75,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// /// The path. /// The directory service. + /// The logger. + /// The file system. /// true if [is music album] [the specified data]; otherwise, false. - public static bool IsMusicAlbum(string path, IDirectoryService directoryService) + public static bool IsMusicAlbum(string path, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) { - return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService); + return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, logger, fileSystem); } /// @@ -75,13 +88,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// /// The args. /// true if [is music album] [the specified args]; otherwise, false. - public static bool IsMusicAlbum(ItemResolveArgs args) + private bool IsMusicAlbum(ItemResolveArgs args) { // Args points to an album if parent is an Artist folder or it directly contains music if (args.IsDirectory) { //if (args.Parent is MusicArtist) return true; //saves us from testing children twice - if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService)) return true; + if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem)) return true; } return false; @@ -93,19 +106,23 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// The list. /// if set to true [allow subfolders]. /// The directory service. + /// The logger. + /// The file system. /// true if the specified list contains music; otherwise, false. - private static bool ContainsMusic(IEnumerable list, bool allowSubfolders, IDirectoryService directoryService) + private static bool ContainsMusic(IEnumerable list, bool allowSubfolders, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; + var discSubfolderCount = 0; + foreach (var fileSystemInfo in list) { if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { - if (allowSubfolders && IsAlbumSubfolder(fileSystemInfo, directoryService)) + if (allowSubfolders && IsAlbumSubfolder(fileSystemInfo, directoryService, logger, fileSystem)) { - return true; + discSubfolderCount++; } if (!IsAdditionalSubfolderAllowed(fileSystemInfo)) { @@ -118,7 +135,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio if (EntityResolutionHelper.IsAudioFile(fullName)) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) + if (string.Equals(fileSystem.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) { continue; } @@ -135,16 +152,18 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio } // or a single audio file and no video files - return foundAudio > 0; + return foundAudio > 0 || discSubfolderCount > 0; } - private static bool IsAlbumSubfolder(FileSystemInfo directory, IDirectoryService directoryService) + private static bool IsAlbumSubfolder(FileSystemInfo directory, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) { var path = directory.FullName; if (IsMultiDiscFolder(path)) { - return ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService); + logger.Debug("Found multi-disc folder: " + path); + + return ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem); } return false; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 4fa97fc9db..19a284d15c 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.IO; using System.Linq; @@ -15,6 +17,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// public class MusicArtistResolver : ItemResolver { + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MusicArtistResolver(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + /// /// Gets the priority. /// @@ -61,7 +72,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var directoryService = args.DirectoryService; // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; + return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName, directoryService, _logger, _fileSystem)) ? new MusicArtist() : null; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs index 594277ef72..166465f72c 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; @@ -12,6 +13,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers /// public class FolderResolver : FolderResolver { + private readonly IFileSystem _fileSystem; + + public FolderResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// /// Gets the priority. /// @@ -69,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers } }) - .Select(i => Path.GetFileNameWithoutExtension(i.FullName)) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs index 10ee3586d7..b483f7c421 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; @@ -11,6 +12,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers /// public class LocalTrailerResolver : BaseVideoResolver { + private readonly IFileSystem _fileSystem; + + public LocalTrailerResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// /// Resolves the specified args. /// @@ -33,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers } // Support xbmc local trailer convention, but only when looking for local trailers (hence the parent == null check) - if (args.Parent == null && Path.GetFileNameWithoutExtension(args.Path).EndsWith(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase)) + if (args.Parent == null && _fileSystem.GetFileNameWithoutExtension(args.Path).EndsWith(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase)) { return base.Resolve(args); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index f3e6fcdba4..215cff22fc 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -22,12 +23,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies private readonly IServerApplicationPaths _applicationPaths; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; - public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger) + public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) { _applicationPaths = appPaths; _libraryManager = libraryManager; _logger = logger; + _fileSystem = fileSystem; } /// @@ -407,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrWhiteSpace(filenamePrefix)) { - if (sortedMovies.All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) + if (sortedMovies.All(i => _fileSystem.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) { firstMovie.HasLocalAlternateVersions = true; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index a756dc7944..519d775ded 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -14,6 +15,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV /// public class SeriesResolver : FolderResolver { + private readonly IFileSystem _fileSystem; + + public SeriesResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// /// Gets the priority. /// @@ -67,8 +75,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { return null; } - - if (TVUtils.IsSeriesFolder(args.Path, collectionType == CollectionType.TvShows, args.FileSystemChildren, args.DirectoryService)) + + if (TVUtils.IsSeriesFolder(args.Path, collectionType == CollectionType.TvShows, args.FileSystemChildren, args.DirectoryService, _fileSystem)) { return new Series(); } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index dbe0209082..43c9f3b9c2 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -282,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.Library /// public async Task CreateUser(string name) { - if (string.IsNullOrEmpty(name)) + if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException("name"); } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json index 53047104cf..d9855c52ff 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -314,10 +314,10 @@ "ButtonSubtitles": "Subt\u00edtulos", "ButtonScenes": "Escenas", "ButtonQuality": "Calidad", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderNotifications": "Notificaciones", + "HeaderSelectPlayer": "Seleccionar Reproductor:", "ButtonSelect": "Seleccionar", "ButtonNew": "Nuevo", - "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", - "HeaderVideoError": "Video Error" + "MessageInternetExplorerWebm": "Para mejores resultados con Internet Explorer por favor instale el complemento WebM para IE.", + "HeaderVideoError": "Error de Video" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json index 6bcb5aed5a..5086dcfacf 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -77,7 +77,7 @@ "MessageInvalidUser": "Nom d'utilisateur et mot de passe invalides.", "HeaderAllRecordings": "Tous les enregistrements", "RecommendationBecauseYouLike": "Parce-que vous aimez {0}", - "RecommendationBecauseYouWatched": "Parece-que vous avez regard\u00e9 {0}", + "RecommendationBecauseYouWatched": "Parce que vous avez regard\u00e9 {0}", "RecommendationDirectedBy": "R\u00e9alis\u00e9 par {0}", "RecommendationStarring": "Mettant en vedette {0}", "HeaderConfirmRecordingCancellation": "Confirmer l'annulation de l'enregistrement.", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json index 191a89000d..37cff9888e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -20,11 +20,11 @@ "OptionRelease": "Offici\u00eble Release", "OptionBeta": "Beta", "OptionDev": "Dev (Instabiel)", - "UninstallPluginHeader": "Invoegtoepassing de\u00efnstalleren", + "UninstallPluginHeader": "Plug-in de\u00efnstalleren", "UninstallPluginConfirmation": "Weet u zeker dat u {0} wilt de\u00efnstalleren?", - "NoPluginConfigurationMessage": "Deze Invoegtoepassing heeft niets in te stellen", - "NoPluginsInstalledMessage": "U heeft geen Invoegtoepassingen ge\u00efnstalleerd", - "BrowsePluginCatalogMessage": "Bekijk de Invoegtoepassings catalogus voor beschikbare Invoegtoepassingen.", + "NoPluginConfigurationMessage": "Deze Plug-in heeft niets in te stellen", + "NoPluginsInstalledMessage": "U heeft geen Plug-in ge\u00efnstalleerd", + "BrowsePluginCatalogMessage": "Bekijk de Plug-in catalogus voor beschikbare Plug-ins.", "MessageKeyEmailedTo": "Sleutel gemaild naar {0}.", "MessageKeysLinked": "Sleutels gekoppeld.", "HeaderConfirmation": "Bevestiging", @@ -45,7 +45,7 @@ "HeaderDeleteTaskTrigger": "Verwijderen Taak Trigger", "HeaderTaskTriggers": "Taak Triggers", "MessageDeleteTaskTrigger": "Weet u zeker dat u deze taak trigger wilt verwijderen?", - "MessageNoPluginsInstalled": "U heeft geen Invoegtoepassingen ge\u00efnstalleerd.", + "MessageNoPluginsInstalled": "U heeft geen Plug-ins ge\u00efnstalleerd.", "LabelVersionInstalled": "{0} ge\u00efnstalleerd", "LabelNumberReviews": "{0} Recensies", "LabelFree": "Gratis", @@ -305,7 +305,7 @@ "TabDLNA": "DLNA", "TabLiveTV": "Live TV", "TabAutoOrganize": "Automatisch-Organiseren", - "TabPlugins": "Invoegtoepassingen", + "TabPlugins": "Plug-ins", "TabAdvanced": "Geavanceerd", "TabHelp": "Hulp", "TabScheduledTasks": "Geplande taken", @@ -318,6 +318,6 @@ "HeaderSelectPlayer": "Selecteer Speler:", "ButtonSelect": "Selecteer", "ButtonNew": "Nieuw", - "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "MessageInternetExplorerWebm": "Voor het beste resultaat met Internet Explorer installeert u de WebM plugin voor IE.", "HeaderVideoError": "Video Fout" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json index 51559053da..622ad2c059 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -318,6 +318,6 @@ "HeaderSelectPlayer": "Selecione onde executar:", "ButtonSelect": "Selecionar", "ButtonNew": "Nova", - "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", - "HeaderVideoError": "Video Error" + "MessageInternetExplorerWebm": "Para melhores resultados com o Internet Explorer, por favor instale o plugin WebM para IE.", + "HeaderVideoError": "Erro de V\u00eddeo" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json index 2fa7f2ea7a..80bdc43f7c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -60,7 +60,7 @@ "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", "ButtonNextTrack": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "ButtonPause": "\u041f\u0430\u0443\u0437\u0430", - "ButtonPlay": "\u0412\u043e\u0441\u043f\u0440", + "ButtonPlay": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438", "ButtonEdit": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c", "ButtonQueue": "\u041e\u0447\u0435\u0440\u0435\u0434\u044c", "ButtonPlayTrailer": "\u0412\u043e\u0441\u043f\u0440 \u0442\u0440\u0435\u0439\u043b\u0435\u0440", @@ -276,7 +276,7 @@ "OptionParentalRating": "\u0412\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u0430\u044f \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f", "OptionPeople": "\u041b\u044e\u0434\u0438", "OptionRuntime": "\u0414\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c", - "OptionProductionLocations": "\u041c\u0435\u0441\u0442\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0430", + "OptionProductionLocations": "\u041c\u0435\u0441\u0442\u0430 \u0441\u044a\u0451\u043c\u043e\u043a", "OptionBirthLocation": "\u041c\u0435\u0441\u0442\u043e \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f", "LabelAllChannels": "\u0412\u0441\u0435 \u043a\u0430\u043d\u0430\u043b\u044b", "LabelLiveProgram": "\u041f\u0420\u042f\u041c\u041e\u0419 \u042d\u0424\u0418\u0420", diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs index 05de4d65aa..1f993f0d4a 100644 --- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs +++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs @@ -235,7 +235,9 @@ namespace MediaBrowser.Server.Implementations.Localization .Where(i => i != null) .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); - var countryCode = Path.GetFileNameWithoutExtension(file).Split('-').Last(); + var countryCode = _fileSystem.GetFileNameWithoutExtension(file) + .Split('-') + .Last(); _allParentalRatings.TryAdd(countryCode, dict); } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ar.json b/MediaBrowser.Server.Implementations/Localization/Server/ar.json index 81922f713b..667245839a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ca.json b/MediaBrowser.Server.Implementations/Localization/Server/ca.json index a56ccf4de3..bcca3aeb9c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/cs.json b/MediaBrowser.Server.Implementations/Localization/Server/cs.json index 729768b78d..71ca6fc498 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Ano", "OptionNo": "Ne", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/da.json b/MediaBrowser.Server.Implementations/Localization/Server/da.json index c2fafba45f..7a5bd76322 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json index 5ffff96dde..ea1b4a1bc5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Ja", "OptionNo": "Nein", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/el.json b/MediaBrowser.Server.Implementations/Localization/Server/el.json index fe7451fed9..2edf373264 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json index 5044e6d959..7ff88733ae 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json index dc4ac80cbc..930401e5cb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json index 9c43a9c9e2..94c6c9ec47 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json index 98a36f8dd7..f1a323e6d1 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -190,7 +190,7 @@ "HeaderStatus": "Estado", "OptionContinuing": "Continuando", "OptionEnded": "Finalizado", - "HeaderAirDays": "D\u00edas de Emisi\u00f3n:", + "HeaderAirDays": "D\u00edas de Emisi\u00f3n", "OptionSunday": "Domingo", "OptionMonday": "Lunes", "OptionTuesday": "Martes", @@ -198,8 +198,8 @@ "OptionThursday": "Jueves", "OptionFriday": "Viernes", "OptionSaturday": "S\u00e1bado", - "HeaderManagement": "Administraci\u00f3n:", - "LabelManagement": "Management:", + "HeaderManagement": "Administraci\u00f3n", + "LabelManagement": "Administraci\u00f3n:", "OptionMissingImdbId": "Falta Id de IMDb", "OptionMissingTvdbId": "Falta Id de TheTVDB", "OptionMissingOverview": "Falta Sinopsis", @@ -257,11 +257,11 @@ "ButtonSelectDirectory": "Seleccionar Carpeta", "LabelCustomPaths": "Especificar rutas personalizadas cuando se desee. Deje los campos vac\u00edos para usar los valores predeterminados.", "LabelCachePath": "Ruta para el Cach\u00e9:", - "LabelCachePathHelp": "Esta carpeta contiene los archivos de cach\u00e9 del servidor, por ejemplo im\u00e1genes.", + "LabelCachePathHelp": "Especifique una ubicaci\u00f3n personalizada para los archivos de cach\u00e9 del servidor, tales como im\u00e1genes.", "LabelImagesByNamePath": "Ruta para Im\u00e1genes por nombre:", - "LabelImagesByNamePathHelp": "Esta carpeta contiene im\u00e1genes descargadas de actores, artistas, g\u00e9neros y estudios.", + "LabelImagesByNamePathHelp": "Especifique una ubicaci\u00f3n personalizada para im\u00e1genes descargadas de actores, artistas, g\u00e9neros y estudios.", "LabelMetadataPath": "Ruta para metadatos:", - "LabelMetadataPathHelp": "Esta ubicaci\u00f3n contiene ilustraciones descargadas y metadatos cuando no han sido configurados para almacenarse en carpetas de medios.", + "LabelMetadataPathHelp": "Especifique una ubicaci\u00f3n personalizada para ilustraciones descargadas y metadatos cuando no han sido configurados para almacenarse en carpetas de medios.", "LabelTranscodingTempPath": "Ruta para transcodificaci\u00f3n temporal:", "LabelTranscodingTempPathHelp": "Esta carpeta contiene archivos de trabajo usados por el transcodificador. Especifique una trayectoria personalizada, o d\u00e9jela vac\u00eda para utilizar su valor por omisi\u00f3n en la carpeta de datos del servidor.", "TabBasics": "B\u00e1sicos", @@ -627,10 +627,10 @@ "TabNowPlaying": "Reproduci\u00e9ndo Ahora", "TabNavigation": "Navegaci\u00f3n", "TabControls": "Controles", - "ButtonFullscreen": "Toggle fullscreen", + "ButtonFullscreen": "Cambiar a pantalla completa", "ButtonScenes": "Escenas", "ButtonSubtitles": "Subt\u00edtulos", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "Pistas de audio", "ButtonPreviousTrack": "Pista anterior", "ButtonNextTrack": "Pista siguiente", "ButtonStop": "Detener", @@ -880,13 +880,13 @@ "TabSort": "Ordenaci\u00f3n", "TabFilter": "Filtro", "ButtonView": "Vista", - "LabelPageSize": "Tama\u00f1o de Pantalla:", + "LabelPageSize": "Cantidad de \u00cdtems:", "LabelView": "Vista:", "TabUsers": "Usuarios", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced", - "ButtonSync": "Sync", + "HeaderFeatures": "Caracter\u00edsticas", + "HeaderAdvanced": "Avanzado", + "ButtonSync": "Sincronizar", "TabScheduledTasks": "Tareas Programadas", - "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderChapters": "Cap\u00edtulos", + "HeaderResumeSettings": "Configuraci\u00f3n para Continuar" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json index f5f3d93879..275cd20c33 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -824,8 +824,8 @@ "LabelProtocolInfoHelp": "La valeur sera utilis\u00e9e par le p\u00e9riph\u00e9rique pour r\u00e9pondre aux requ\u00eates GetProtocolInfo.", "TabXbmcMetadata": "Xbmc", "HeaderXbmcMetadataHelp": "Media Browser inclut un support natif pour les m\u00e9tadonn\u00e9es Nfo Xbmc et les images. Pour activer ou d\u00e9sactiver les m\u00e9tadonn\u00e9es Xbmc, utiliser l'onglet Avanc\u00e9 pour configurer les options de vos types de m\u00e9dia.", - "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", - "LabelXbmcMetadataUserHelp": "Activer ceci pour synchroniser en permanence Media Browser et Xbmc", + "LabelXbmcMetadataUser": "Ajouter aux nfo, les donn\u00e9es de surveillance de l'utilisateur :", + "LabelXbmcMetadataUserHelp": "Activer ceci pour synchroniser les donn\u00e9es de surveillance entre Media Browser et Xbmc", "LabelXbmcMetadataDateFormat": "Format de la date de sortie :", "LabelXbmcMetadataDateFormatHelp": "Toutes les dates provenant des nfo seront lues et \u00e9crites en utilisant ce format.", "LabelXbmcMetadataSaveImagePaths": "Sauvegarder les chemins d'images dans les fichiers nfo.", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "T\u00e2ches planifi\u00e9es", "HeaderChapters": "Chapitres", - "HeaderResumeSettings": "Reprendre les param\u00e8tres" + "HeaderResumeSettings": "Reprendre les param\u00e8tres", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/he.json b/MediaBrowser.Server.Implementations/Localization/Server/he.json index 43135ec818..51a27549e7 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/it.json b/MediaBrowser.Server.Implementations/Localization/Server/it.json index e76d47a044..dba80e2b4f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Operazioni pianificate", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/kk.json b/MediaBrowser.Server.Implementations/Localization/Server/kk.json index 9a1847ea45..a3120ffe4b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -888,5 +888,7 @@ "ButtonSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443", "TabScheduledTasks": "\u0416\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0443\u0448\u044b", "HeaderChapters": "\u0421\u0430\u0445\u043d\u0430\u043b\u0430\u0440", - "HeaderResumeSettings": "\u0416\u0430\u043b\u0493\u0430\u0441\u0442\u044b\u0440\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456" + "HeaderResumeSettings": "\u0416\u0430\u043b\u0493\u0430\u0441\u0442\u044b\u0440\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ko.json b/MediaBrowser.Server.Implementations/Localization/Server/ko.json index f474225fd9..c558cbe8dc 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ko.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ko.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ms.json b/MediaBrowser.Server.Implementations/Localization/Server/ms.json index 232e94a72a..134ff44d8e 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nb.json b/MediaBrowser.Server.Implementations/Localization/Server/nb.json index c10e19102b..165bf3f66d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json index c1a8710959..5862688a60 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -39,7 +39,7 @@ "HeaderSetupLibrary": "Stel uw mediabibliotheek in", "ButtonAddMediaFolder": "Mediamap toevoegen", "LabelFolderType": "Maptype:", - "MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een Invoegtoepassing vereist, bijvoorbeeld GameBrowser of MB Bookshelf.", + "MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een Plug-in vereist, bijvoorbeeld GameBrowser of MB Bookshelf.", "ReferToMediaLibraryWiki": "Raadpleeg de mediabibliotheek wiki.", "LabelCountry": "Land:", "LabelLanguage": "Taal:", @@ -152,16 +152,16 @@ "OptionResumable": "Hervatbaar", "ScheduledTasksHelp": "Klik op een taak om het schema aan te passen.", "ScheduledTasksTitle": "Geplande taken", - "TabMyPlugins": "Mijn Invoegtoepassingen", + "TabMyPlugins": "Mijn Plug-ins", "TabCatalog": "Catalogus", - "PluginsTitle": "Invoegtoepassingen", + "PluginsTitle": "Plug-ins", "HeaderAutomaticUpdates": "Automatische updates", "HeaderNowPlaying": "Wordt nu afgespeeld", "HeaderLatestAlbums": "Nieuwste Albums", "HeaderLatestSongs": "Nieuwste Songs", "HeaderRecentlyPlayed": "Recent afgespeeld", "HeaderFrequentlyPlayed": "Vaak afgespeeld", - "DevBuildWarning": "Development versies zijn voor eigen risico. Ze worden vaak uitgegeven, deze versies zijn niet getest!. De Applicatie kan crashen en sommige functies kunnen mogelijk niet meer werken.", + "DevBuildWarning": "Development versies zijn geheel voor eigen risico. Deze versies worden vaak vrijgegeven en zijn niet getest!. De Applicatie kan crashen en sommige functies kunnen mogelijk niet meer werken.", "LabelVideoType": "Video Type:", "OptionBluray": "Blu-ray", "OptionDvd": "Dvd", @@ -249,11 +249,11 @@ "OptionRelease": "Offici\u00eble Release", "OptionBeta": "Beta", "OptionDev": "Dev (Instabiel)", - "LabelAllowServerAutoRestart": "Sta de server toe automatisch te herstarten om updates toe te passen", - "LabelAllowServerAutoRestartHelp": "De server zal alleen opnieuw op tijdens inactieve perioden, wanneer er geen gebruikers actief zijn.", + "LabelAllowServerAutoRestart": "Automatisch herstarten van de server toestaan om updates toe te passen", + "LabelAllowServerAutoRestartHelp": "De server zal alleen opnieuw opstarten tijdens inactieve perioden, wanneer er geen gebruikers actief zijn.", "LabelEnableDebugLogging": "Foutopsporings logboek inschakelen", "LabelRunServerAtStartup": "Start server bij het aanmelden", - "LabelRunServerAtStartupHelp": "Dit zal de applicatie starten als pictogram in het systeemvak tijdens het aanmelden van Windows. Om de Windows-service te starten, schakelt u deze uit en start u de service via het Configuratiescherm van Windows. Houd er rekening mee dat u de service en de applicatie niet tegelijk kunt uitvoeren, u moet dus eerst de applicatie sluiten alvorens u de service start.", + "LabelRunServerAtStartupHelp": "Dit start de applicatie als pictogram in het systeemvak tijdens het aanmelden van Windows. Om de Windows-service te starten, schakelt u deze uit en start u de service via het Configuratiescherm van Windows. Houd er rekening mee dat u de service en de applicatie niet tegelijk kunt uitvoeren, u moet dus eerst de applicatie sluiten alvorens u de service start.", "ButtonSelectDirectory": "Selecteer map", "LabelCustomPaths": "Geef aangepaste paden op waar gewenst. Laat velden leeg om de standaardinstellingen te gebruiken.", "LabelCachePath": "Cache pad:", @@ -333,10 +333,10 @@ "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", "LabelActiveService": "Actieve Service:", - "LabelActiveServiceHelp": "Er kunnen meerdere tv Invoegtoepassingen worden ge\u00efnstalleerd, maar er kan er slechts een per keer actief zijn.", + "LabelActiveServiceHelp": "Er kunnen meerdere tv Plug-ins worden ge\u00efnstalleerd, maar er kan er slechts een per keer actief zijn.", "OptionAutomatic": "Automatisch", - "LiveTvPluginRequired": "Een Live TV service provider Invoegtoepassing is vereist om door te gaan.", - "LiveTvPluginRequiredHelp": "Installeer a.u b een van onze beschikbare Invoegtoepassingen, zoals Next PVR of ServerWmc.", + "LiveTvPluginRequired": "Een Live TV service provider Plug-in is vereist om door te gaan.", + "LiveTvPluginRequiredHelp": "Installeer a.u b een van onze beschikbare Plug-ins, zoals Next PVR of ServerWmc.", "LabelCustomizeOptionsPerMediaType": "Aanpassen voor mediatype", "OptionDownloadThumbImage": "Miniatuur", "OptionDownloadMenuImage": "Menu", @@ -577,9 +577,9 @@ "HeaderNotificationList": "Klik op een melding om de opties voor het versturen ervan te configureren .", "NotificationOptionApplicationUpdateAvailable": "Programma-update beschikbaar", "NotificationOptionApplicationUpdateInstalled": "Programma-update ge\u00efnstalleerd", - "NotificationOptionPluginUpdateInstalled": "Invoegtoepassings-update ge\u00efnstalleerd", - "NotificationOptionPluginInstalled": "Invoegtoepassing ge\u00efnstalleerd", - "NotificationOptionPluginUninstalled": "Invoegtoepassing verwijderd", + "NotificationOptionPluginUpdateInstalled": "Plug-in-update ge\u00efnstalleerd", + "NotificationOptionPluginInstalled": "Plug-in ge\u00efnstalleerd", + "NotificationOptionPluginUninstalled": "Plug-in verwijderd", "NotificationOptionVideoPlayback": "Video afspelen gestart", "NotificationOptionAudioPlayback": "Audio afspelen gestart", "NotificationOptionGamePlayback": "Game gestart", @@ -590,7 +590,7 @@ "NotificationOptionInstallationFailed": "Mislukken van de installatie", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionNewLibraryContentMultiple": "Nieuwe content toegevoegd (meerdere)", - "SendNotificationHelp": "Meldingen worden geplaatst in de inbox op het dashboard. Blader door de Invoegtoepassings catalogus om aanvullende opties voor meldingen te installeren.", + "SendNotificationHelp": "Meldingen worden geplaatst in de inbox op het dashboard. Blader door de Plug-in catalogus om aanvullende opties voor meldingen te installeren.", "NotificationOptionServerRestartRequired": "Server herstart nodig", "LabelNotificationEnabled": "Deze melding inschakelen", "LabelMonitorUsers": "Monitor activiteit van:", @@ -600,10 +600,10 @@ "CategoryUser": "Gebruiker", "CategorySystem": "Systeem", "CategoryApplication": "Toepassing", - "CategoryPlugin": "Invoegtoepassing", + "CategoryPlugin": "Plug-in", "LabelMessageTitle": "Titel van het bericht:", "LabelAvailableTokens": "Beschikbaar tokens:", - "AdditionalNotificationServices": "Blader door de Invoegtoepassings catalogus om aanvullende meldingsdiensten te installeren.", + "AdditionalNotificationServices": "Blader door de Plug-in catalogus om aanvullende meldingsdiensten te installeren.", "OptionAllUsers": "Alle gebruikers", "OptionAdminUsers": "Beheerders", "OptionCustomUsers": "Aangepast", @@ -637,7 +637,7 @@ "ButtonPause": "Pauze", "LabelGroupMoviesIntoCollections": "Groepeer films in verzamelingen", "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films die behoren tot een verzameling worden weergegeven als een gegroepeerd object.", - "NotificationOptionPluginError": "Invoegtoepassings fout", + "NotificationOptionPluginError": "Plug-in fout", "ButtonVolumeUp": "Volume omhoog", "ButtonVolumeDown": "Volume omlaag", "ButtonMute": "Dempen", @@ -723,7 +723,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Dit is vereist voor bepaalde apparaten die zo goed op tijd zoeken.", "HeaderSubtitleDownloadingHelp": "Bij het scannen van uw videobestanden kan Media Browser naar ontbrekende ondertiteling zoeken en deze downloaden bij ondertiteling providers zoals OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "Download ondertiteling voor:", - "MessageNoChapterProviders": "Installeer een hoofdstuk provider Invoegtoepassing zoals ChapterDb om extra hoofdstuk opties in te schakelen.", + "MessageNoChapterProviders": "Installeer een hoofdstuk provider Plug-in zoals ChapterDb om extra hoofdstuk opties in te schakelen.", "LabelSkipIfGraphicalSubsPresent": "Overslaan als de video al grafische ondertitels bevat", "LabelSkipIfGraphicalSubsPresentHelp": "Tekstversies houden van ondertitels zal resulteren in meer effici\u00ebnte levering aan mobiele clients.", "TabSubtitles": "Ondertiteling", @@ -731,7 +731,7 @@ "HeaderDownloadChaptersFor": "Download hoofdstuk namen voor:", "LabelOpenSubtitlesUsername": "Gebruikersnaam Open Subtitles:", "LabelOpenSubtitlesPassword": "Wachtwoord Open Subtitles:", - "HeaderChapterDownloadingHelp": "Wanneer Media Browser uw videobestanden scant kan het gebruiksvriendelijke namen voor hoofdstukken downloaden van het internet met behulp van hoofdstuk Invoegtoepassing zoals ChapterDb.", + "HeaderChapterDownloadingHelp": "Wanneer Media Browser uw videobestanden scant kan het gebruiksvriendelijke namen voor hoofdstukken downloaden van het internet met behulp van hoofdstuk Plug-in zoals ChapterDb.", "LabelPlayDefaultAudioTrack": "Speel standaard audio spoor ongeacht taal", "LabelSubtitlePlaybackMode": "Ondertitelingsmode:", "LabelDownloadLanguages": "Download talen:", @@ -741,8 +741,8 @@ "HeaderSendMessage": "Stuur bericht", "ButtonSend": "Stuur", "LabelMessageText": "Bericht tekst:", - "MessageNoAvailablePlugins": "Geen beschikbare Invoegtoepassingen.", - "LabelDisplayPluginsFor": "Toon Invoegtoepassingen voor:", + "MessageNoAvailablePlugins": "Geen beschikbare Plug-ins.", + "LabelDisplayPluginsFor": "Toon Plug-ins voor:", "PluginTabMediaBrowserClassic": "MB Classic", "PluginTabMediaBrowserTheater": "MB Theater", "LabelEpisodeName": "Naam aflevering", @@ -803,7 +803,7 @@ "LabelChannelDownloadPathHelp": "Geef een eigen download pad op als dit gewenst is, leeglaten voor dowloaden naar de interne program data map.", "LabelChannelDownloadAge": "Verwijder inhoud na: (dagen)", "LabelChannelDownloadAgeHelp": "Gedownloade inhoud die ouder is zal worden verwijderd. Afspelen via internet streaming blijft mogelijk.", - "ChannelSettingsFormHelp": "Installeer kanalen zoals Trailers en Vimeo in de Invoegtoepassings catalogus.", + "ChannelSettingsFormHelp": "Installeer kanalen zoals Trailers en Vimeo in de Plug-in catalogus.", "LabelSelectCollection": "Selecteer verzameling:", "ViewTypeMovies": "Films", "ViewTypeTvShows": "TV", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Geplande taken", "HeaderChapters": "Hoofdstukken", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Instellingen voor Hervatten", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pl.json b/MediaBrowser.Server.Implementations/Localization/Server/pl.json index 3032f71f08..135b640f28 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json index a80d352b92..55d76aa8b6 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -190,7 +190,7 @@ "HeaderStatus": "Status", "OptionContinuing": "Em Exibi\u00e7\u00e3o", "OptionEnded": "Finalizada", - "HeaderAirDays": "Dias de Exibi\u00e7\u00e3o:", + "HeaderAirDays": "Dias de Exibi\u00e7\u00e3o", "OptionSunday": "Domingo", "OptionMonday": "Segunda-feira", "OptionTuesday": "Ter\u00e7a-feira", @@ -198,7 +198,7 @@ "OptionThursday": "Quinta-feira", "OptionFriday": "Sexta-feira", "OptionSaturday": "S\u00e1bado", - "HeaderManagement": "Gerenciamento:", + "HeaderManagement": "Gerenciamento", "LabelManagement": "Administra\u00e7\u00e3o:", "OptionMissingImdbId": "Faltando Id IMDb", "OptionMissingTvdbId": "Faltando Id TheTVDB", @@ -257,11 +257,11 @@ "ButtonSelectDirectory": "Selecionar Diret\u00f3rio", "LabelCustomPaths": "Defina caminhos personalizados. Deixe os campos em branco para usar o padr\u00e3o.", "LabelCachePath": "Caminho do cache:", - "LabelCachePathHelp": "Esta pasta cont\u00e9m arquivos de cache do servidor como, por exemplo, imagens.", + "LabelCachePathHelp": "Defina uma localiza\u00e7\u00e3o para os arquivos de cache como, por exemplo, imagens.", "LabelImagesByNamePath": "Caminho do Images by name:", - "LabelImagesByNamePathHelp": "Esta pasta cont\u00e9m imagens transferidas para ator, artista, g\u00eanero e est\u00fadio.", + "LabelImagesByNamePathHelp": "Defina uma localiza\u00e7\u00e3o para imagens baixadas para ator, artista, g\u00eanero e est\u00fadio.", "LabelMetadataPath": "Caminho dos Metadados:", - "LabelMetadataPathHelp": "Esta localiza\u00e7\u00e3o cont\u00e9m artwork e metadados transferidos que n\u00e3o foram configurados para serem armazenados nas pastas de m\u00eddia.", + "LabelMetadataPathHelp": "Defina uma localiza\u00e7\u00e3o para artwork e metadados baixados, caso n\u00e3o sejam salvos dentro das pastas de m\u00eddia.", "LabelTranscodingTempPath": "Caminho tempor\u00e1rio para transcodifica\u00e7\u00e3o:", "LabelTranscodingTempPathHelp": "Esta pasta cont\u00e9m arquivos ativos usados pelo transcodificador. Especifique um caminho personalizado ou deixe em branco para usar o padr\u00e3o dentro da pasta de dados do servidor.", "TabBasics": "B\u00e1sico", @@ -692,7 +692,7 @@ "LabelMaxBitrate": "Taxa de bits m\u00e1xima:", "LabelMaxBitrateHelp": "Especifique uma taxa de bits m\u00e1xima para ambientes com restri\u00e7\u00e3o de tamanho de banda, ou se o dispositivo imp\u00f5e esse limite.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorar requisi\u00e7\u00f5es de extens\u00e3o do byte de transcodifica\u00e7\u00e3o", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Se ativado, estas requisi\u00e7\u00f5es ser\u00e3o honradas mas ir\u00e3o ignorar o cabe\u00e7alho da extens\u00e3o do byte.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Se ativadas, estas requisi\u00e7\u00f5es ser\u00e3o honradas mas ir\u00e3o ignorar o cabe\u00e7alho da extens\u00e3o do byte.", "LabelFriendlyName": "Nome amig\u00e1vel", "LabelManufacturer": "Fabricante", "LabelManufacturerUrl": "Url do fabricante", @@ -834,7 +834,7 @@ "LabelXbmcMetadataEnablePathSubstitutionHelp": "Ativa a substitui\u00e7\u00e3o do caminho da imagem usando as configura\u00e7\u00f5es de suvbstitui\u00e7\u00e3o de caminho do servidor.", "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Ver substitui\u00e7\u00e3o de caminho.", "LabelGroupChannelsIntoViews": "Exibir os seguintes canais diretamente dentro de minhas visualiza\u00e7\u00f5es:", - "LabelGroupChannelsIntoViewsHelp": "Se ativado, estes canais ser\u00e3o exibidos imediatamente ao lado de outras visualiza\u00e7\u00f5es. Se desativado, eles ser\u00e3o exibidos dentro de uma visualiza\u00e7\u00e3o separada de Canais.", + "LabelGroupChannelsIntoViewsHelp": "Se ativados, estes canais ser\u00e3o exibidos imediatamente ao lado de outras visualiza\u00e7\u00f5es. Se desativado, eles ser\u00e3o exibidos dentro de uma visualiza\u00e7\u00e3o separada de Canais.", "LabelDisplayCollectionsView": "Exibir uma visualiza\u00e7\u00e3o de cole\u00e7\u00f5es para mostrar colet\u00e2neas de filmes", "LabelXbmcMetadataEnableExtraThumbs": "Copiar extrafanart para dentro de extrathumbs", "LabelXbmcMetadataEnableExtraThumbsHelp": "Ao fazer o download de imagens elas podem ser salvas em ambas extrafanart e extrathumbs para uma maior compatibilidade com a skin do Xbmc.", @@ -842,7 +842,7 @@ "TabLogs": "Logs", "HeaderServerLogFiles": "Arquivos de log do servidor:", "TabBranding": "Marca", - "HeaderBrandingHelp": "Personalizar a apar\u00eancia do Media Browser para as necessidades de seu grupo ou organiza\u00e7\u00e3o.", + "HeaderBrandingHelp": "Personalize a apar\u00eancia do Media Browser para as necessidades de seu grupo ou organiza\u00e7\u00e3o.", "LabelLoginDisclaimer": "Aviso legal no login:", "LabelLoginDisclaimerHelp": "Isto ser\u00e1 exibido na parte inferior da p\u00e1gina de login.", "LabelAutomaticallyDonate": "Doar automaticamente esta quantidade a cada seis meses", @@ -880,13 +880,15 @@ "TabSort": "Ordenar", "TabFilter": "Filtro", "ButtonView": "Visualizar", - "LabelPageSize": "Tamanho de exibi\u00e7\u00e3o:", + "LabelPageSize": "Limite do item:", "LabelView": "Visualizar:", "TabUsers": "Usu\u00e1rios", "HeaderFeatures": "Inclui", "HeaderAdvanced": "Avan\u00e7ado", - "ButtonSync": "Sync", + "ButtonSync": "Sincronizar", "TabScheduledTasks": "Tarefas Agendadas", - "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderChapters": "Cap\u00edtulos", + "HeaderResumeSettings": "Ajustes para Retomar", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json index 83e82070a4..966365f7c0 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json index abdb98e976..c5ea589e86 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -322,7 +322,7 @@ "HeaderActiveRecordings": "\u0410\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438", "HeaderLatestRecordings": "\u041d\u043e\u0432\u0438\u043d\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439", "HeaderAllRecordings": "\u0412\u0441\u0435 \u0437\u0430\u043f\u0438\u0441\u0438", - "ButtonPlay": "\u0412\u043e\u0441\u043f\u0440", + "ButtonPlay": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438", "ButtonEdit": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c", "ButtonRecord": "\u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c", "ButtonDelete": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c", @@ -551,7 +551,7 @@ "LabelSupporterKey": "\u041a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 (\u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0438\u0437 \u043f\u0438\u0441\u044c\u043c\u0430 \u043f\u043e \u042d-\u043f\u043e\u0447\u0442\u0435)", "LabelSupporterKeyHelp": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u044b \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e\u043c \u0434\u043b\u044f Media Browser.", "MessageInvalidKey": "\u041a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u043b\u0438 \u043d\u0435\u0432\u0435\u0440\u0435\u043d", - "ErrorMessageInvalidKey": "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u043f\u0440\u0435\u043c\u0438\u0443\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435, \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u043e\u043c Media Browser. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0434\u0430\u0440\u0441\u0442\u0432\u0443\u0439\u0442\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e\u0449\u0435\u0435\u0441\u044f \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043e\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u0438\u043c \u0432\u0430\u0441.", + "ErrorMessageInvalidKey": "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u043f\u0440\u0435\u043c\u0438\u0443\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u043e\u043c Media Browser. \u0416\u0435\u0440\u0442\u0432\u0443\u0439\u0442\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0443\u044e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043e\u0441\u043d\u043e\u0432\u043e\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.", "HeaderDisplaySettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", "TabPlayTo": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u041d\u0430", "LabelEnableDlnaServer": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DLNA-\u0441\u0435\u0440\u0432\u0435\u0440", @@ -845,8 +845,8 @@ "HeaderBrandingHelp": "\u041f\u043e\u0434\u0433\u043e\u043d\u043a\u0430 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0432\u0438\u0434\u0430 Media Browser \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432\u0430\u0448\u0435\u0439 \u0433\u0440\u0443\u043f\u043f\u044b \u0438\u043b\u0438 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438.", "LabelLoginDisclaimer": "\u041e\u0433\u043e\u0432\u043e\u0440\u043a\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0432\u0445\u043e\u0434\u0430:", "LabelLoginDisclaimerHelp": "\u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u043d\u0438\u0436\u043d\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443.", - "LabelAutomaticallyDonate": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u0430\u0440\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u043a\u0430\u0436\u0434\u044b\u0435 \u0448\u0435\u0441\u0442\u044c \u043c\u0435\u0441\u044f\u0446\u0435\u0432", - "LabelAutomaticallyDonateHelp": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c PayPal.", + "LabelAutomaticallyDonate": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0436\u0435\u0440\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u043a\u0430\u0436\u0434\u044b\u0435 \u0448\u0435\u0441\u0442\u044c \u043c\u0435\u0441\u044f\u0446\u0435\u0432", + "LabelAutomaticallyDonateHelp": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c PayPal.", "OptionList": "\u0421\u043f\u0438\u0441\u043e\u043a", "TabDashboard": "\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c", "TitleServer": "\u0421\u0435\u0440\u0432\u0435\u0440", @@ -878,15 +878,17 @@ "OptionSubstring": "\u041f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0430", "TabView": "\u0412\u0438\u0434", "TabSort": "\u0421\u043e\u0440\u0442-\u043a\u0430", - "TabFilter": "\u0424\u0438\u043b\u044c\u0442\u0440-\u043a\u0430", + "TabFilter": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b", "ButtonView": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c", "LabelPageSize": "\u041c\u0430\u043a\u0441. \u0447\u0438\u0441\u043b\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432:", - "LabelView": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440:", + "LabelView": "\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435:", "TabUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", "HeaderFeatures": "\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b", "HeaderAdvanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e", - "ButtonSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c", + "ButtonSync": "\u0421\u0438\u043d\u0445\u0440\u043e", "TabScheduledTasks": "\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a", "HeaderChapters": "\u0421\u0446\u0435\u043d\u044b", - "HeaderResumeSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f" + "HeaderResumeSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f", + "TabSync": "Sync", + "TitleUsers": "Users" } \ 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 3a49024f93..8b3a5e8c9e 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -781,10 +781,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -897,11 +897,13 @@ "ButtonView": "View", "LabelPageSize": "Item limit:", "LabelView": "View:", - "TabUsers": "Users", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced", - "ButtonSync": "Sync", - "TabScheduledTasks": "Scheduled Tasks", - "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "TabUsers": "Users", + "HeaderFeatures": "Features", + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index 9988af0fa4..c6c0426408 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/vi.json b/MediaBrowser.Server.Implementations/Localization/Server/vi.json index 98f1084215..17748ecc7f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json index 6511dd7d06..352a031e1b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -767,10 +767,10 @@ "OptionAuto": "Auto", "OptionYes": "Yes", "OptionNo": "No", - "LabelHomePageSection1": "Home page section one:", - "LabelHomePageSection2": "Home page section two:", - "LabelHomePageSection3": "Home page section three:", - "LabelHomePageSection4": "Home page section four:", + "LabelHomePageSection1": "Home page section 1:", + "LabelHomePageSection2": "Home page section 2:", + "LabelHomePageSection3": "Home page section 3:", + "LabelHomePageSection4": "Home page section 4:", "OptionMyViewsButtons": "My views (buttons)", "OptionMyViews": "My views", "OptionMyViewsSmall": "My views (small)", @@ -888,5 +888,7 @@ "ButtonSync": "Sync", "TabScheduledTasks": "Scheduled Tasks", "HeaderChapters": "Chapters", - "HeaderResumeSettings": "Resume Settings" + "HeaderResumeSettings": "Resume Settings", + "TabSync": "Sync", + "TitleUsers": "Users" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9655771c66..ef6730b75b 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -45,9 +45,9 @@ v4.5 - + False - ..\packages\Mono.Nat.1.2.20.0\lib\net40\Mono.Nat.dll + ..\packages\Mono.Nat.1.2.21.0\lib\net40\Mono.Nat.dll ..\ThirdParty\Nowin\Nowin.dll @@ -274,6 +274,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 50f1030f3c..a8d723ce3c 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -1,9 +1,14 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; +using MoreLinq; using System; using System.Collections.Generic; using System.Linq; @@ -13,56 +18,131 @@ namespace MediaBrowser.Server.Implementations.Sync { public class SyncManager : ISyncManager { - private ISyncProvider[] _providers = new ISyncProvider[] { }; + private readonly ILibraryManager _libraryManager; + private readonly ISyncRepository _repo; + private readonly IImageProcessor _imageProcessor; + private readonly ILogger _logger; + + private ISyncProvider[] _providers = { }; + + public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger) + { + _libraryManager = libraryManager; + _repo = repo; + _imageProcessor = imageProcessor; + _logger = logger; + } public void AddParts(IEnumerable providers) { _providers = providers.ToArray(); } - public Task> CreateJob(SyncJobRequest request) + public async Task CreateJob(SyncJobRequest request) { - throw new NotImplementedException(); + var items = GetItemsForSync(request.ItemIds).ToList(); + + if (items.Count == 1) + { + request.Name = GetDefaultName(items[0]); + } + + if (string.IsNullOrWhiteSpace(request.Name)) + { + throw new ArgumentException("Please supply a name for the sync job."); + } + + var target = GetSyncTargets(request.UserId) + .First(i => string.Equals(request.TargetId, i.Id)); + + var jobId = Guid.NewGuid().ToString("N"); + + var job = new SyncJob + { + Id = jobId, + Name = request.Name, + TargetId = target.Id, + UserId = request.UserId, + UnwatchedOnly = request.UnwatchedOnly, + Limit = request.Limit, + LimitType = request.LimitType, + RequestedItemIds = request.ItemIds, + DateCreated = DateTime.UtcNow, + DateLastModified = DateTime.UtcNow + }; + + await _repo.Create(job).ConfigureAwait(false); + + return new SyncJobCreationResult + { + Job = GetJob(jobId) + }; } public QueryResult GetJobs(SyncJobQuery query) { - throw new NotImplementedException(); - } + var result = _repo.GetJobs(query); - public QueryResult GetSchedules(SyncScheduleQuery query) - { - throw new NotImplementedException(); - } + result.Items.ForEach(FillMetadata); - public Task CancelJob(string id) - { - throw new NotImplementedException(); + return result; } - public Task CancelSchedule(string id) + private void FillMetadata(SyncJob job) { - throw new NotImplementedException(); + var item = GetItemsForSync(job.RequestedItemIds) + .FirstOrDefault(); + + if (item != null) + { + var hasSeries = item as IHasSeries; + if (hasSeries != null) + { + job.ParentName = hasSeries.SeriesName; + } + + var hasAlbumArtist = item as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + job.ParentName = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + } + + var primaryImage = item.GetImageInfo(ImageType.Primary, 0); + + if (primaryImage != null) + { + try + { + job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); + job.PrimaryImageItemId = item.Id.ToString("N"); + + } + catch (Exception ex) + { + _logger.ErrorException("Error getting image info", ex); + } + } + } } - public SyncJob GetJob(string id) + public Task CancelJob(string id) { throw new NotImplementedException(); } - public SyncSchedule GetSchedule(string id) + public SyncJob GetJob(string id) { - throw new NotImplementedException(); + return _repo.GetJob(id); } public IEnumerable GetSyncTargets(string userId) { return _providers - .SelectMany(GetSyncTargets) + .SelectMany(i => GetSyncTargets(i, userId)) .OrderBy(i => i.Name); } - private IEnumerable GetSyncTargets(ISyncProvider provider) + private IEnumerable GetSyncTargets(ISyncProvider provider, string userId) { var providerId = GetSyncProviderId(provider); @@ -120,5 +200,37 @@ namespace MediaBrowser.Server.Implementations.Sync return false; } + + private IEnumerable GetItemsForSync(IEnumerable itemIds) + { + return itemIds.SelectMany(GetItemsForSync).DistinctBy(i => i.Id); + } + + private IEnumerable GetItemsForSync(string id) + { + var item = _libraryManager.GetItemById(id); + + if (item == null) + { + throw new ArgumentException("Item with Id " + id + " not found."); + } + + if (!SupportsSync(item)) + { + throw new ArgumentException("Item with Id " + id + " does not support sync."); + } + + return GetItemsForSync(item); + } + + private IEnumerable GetItemsForSync(BaseItem item) + { + return new[] { item }; + } + + private string GetDefaultName(BaseItem item) + { + return item.Name; + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs new file mode 100644 index 0000000000..bb22e992b6 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -0,0 +1,429 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Sync; +using MediaBrowser.Server.Implementations.Persistence; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class SyncRepository : ISyncRepository + { + private IDbConnection _connection; + private readonly ILogger _logger; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private IDbCommand _saveJobCommand; + private IDbCommand _saveJobItemCommand; + + public SyncRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "sync.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, UnwatchedOnly BIT, SyncLimit BigInt, LimitType TEXT, IsDynamic BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", + "create index if not exists idx_SyncJobs on SyncJobs(Id)", + + "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT)", + "create index if not exists idx_SyncJobItems on SyncJobs(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveJobCommand = _connection.CreateCommand(); + _saveJobCommand.CommandText = "replace into SyncJobs (Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, SyncLimit, LimitType, IsDynamic, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Quality, @Status, @Progress, @UserId, @ItemIds, @UnwatchedOnly, @SyncLimit, @LimitType, @IsDynamic, @DateCreated, @DateLastModified, @ItemCount)"; + + _saveJobCommand.Parameters.Add(_saveJobCommand, "@Id"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@TargetId"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@Name"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@Quality"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@Status"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@Progress"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@UserId"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemIds"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@UnwatchedOnly"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@SyncLimit"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@LimitType"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@IsDynamic"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@DateCreated"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@DateLastModified"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemCount"); + + _saveJobItemCommand = _connection.CreateCommand(); + _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, JobId, OutputPath, Status, TargetId) values (@Id, @ItemId, @JobId, @OutputPath, @Status, @TargetId)"; + + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Id"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@ItemId"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@JobId"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@OutputPath"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Status"); + } + + private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, SyncLimit, LimitType, IsDynamic, DateCreated, DateLastModified, ItemCount from SyncJobs"; + private const string BaseJobItemSelectText = "select Id, ItemId, JobId, OutputPath, Status, TargetId from SyncJobItems"; + + public SyncJob GetJob(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseJobSelectText + " where Id=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + return GetJob(reader); + } + } + } + + return null; + } + + private SyncJob GetJob(IDataReader reader) + { + var info = new SyncJob + { + Id = reader.GetGuid(0).ToString("N"), + TargetId = reader.GetString(1), + Name = reader.GetString(2) + }; + + if (!reader.IsDBNull(3)) + { + info.Quality = (SyncQuality)Enum.Parse(typeof(SyncQuality), reader.GetString(3), true); + } + + if (!reader.IsDBNull(4)) + { + info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader.GetString(4), true); + } + + if (!reader.IsDBNull(5)) + { + info.Progress = reader.GetDouble(5); + } + + if (!reader.IsDBNull(6)) + { + info.UserId = reader.GetString(6); + } + + if (!reader.IsDBNull(7)) + { + info.RequestedItemIds = reader.GetString(7).Split(',').ToList(); + } + + if (!reader.IsDBNull(8)) + { + info.UnwatchedOnly = reader.GetBoolean(8); + } + + if (!reader.IsDBNull(9)) + { + info.Limit = reader.GetInt64(9); + } + + if (!reader.IsDBNull(10)) + { + info.LimitType = (SyncLimitType)Enum.Parse(typeof(SyncLimitType), reader.GetString(10), true); + } + + info.IsDynamic = reader.GetBoolean(11); + info.DateCreated = reader.GetDateTime(12).ToUniversalTime(); + info.DateLastModified = reader.GetDateTime(13).ToUniversalTime(); + info.ItemCount = reader.GetInt32(14); + + return info; + } + + public Task Create(SyncJob job) + { + return Update(job); + } + + public async Task Update(SyncJob job) + { + if (job == null) + { + throw new ArgumentNullException("job"); + } + + await _writeLock.WaitAsync().ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveJobCommand.GetParameter(index++).Value = new Guid(job.Id); + _saveJobCommand.GetParameter(index++).Value = job.TargetId; + _saveJobCommand.GetParameter(index++).Value = job.Name; + _saveJobCommand.GetParameter(index++).Value = job.Quality; + _saveJobCommand.GetParameter(index++).Value = job.Status; + _saveJobCommand.GetParameter(index++).Value = job.Progress; + _saveJobCommand.GetParameter(index++).Value = job.UserId; + _saveJobCommand.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray()); + _saveJobCommand.GetParameter(index++).Value = job.UnwatchedOnly; + _saveJobCommand.GetParameter(index++).Value = job.Limit; + _saveJobCommand.GetParameter(index++).Value = job.LimitType; + _saveJobCommand.GetParameter(index++).Value = job.IsDynamic; + _saveJobCommand.GetParameter(index++).Value = job.DateCreated; + _saveJobCommand.GetParameter(index++).Value = job.DateLastModified; + _saveJobCommand.GetParameter(index++).Value = job.ItemCount; + + _saveJobCommand.Transaction = transaction; + + _saveJobCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + + public QueryResult GetJobs(SyncJobQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseJobSelectText; + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (startIndex > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY DateLastModified DESC LIMIT {0})", + startIndex.ToString(_usCulture))); + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateLastModified DESC"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from SyncJobs"; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(GetJob(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public SyncJobItem GetJobItem(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseJobItemSelectText + " where Id=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + return GetSyncJobItem(reader); + } + } + } + + return null; + } + + public Task Create(SyncJobItem jobItem) + { + return Update(jobItem); + } + + public async Task Update(SyncJobItem jobItem) + { + if (jobItem == null) + { + throw new ArgumentNullException("jobItem"); + } + + await _writeLock.WaitAsync().ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveJobItemCommand.GetParameter(index++).Value = new Guid(jobItem.Id); + _saveJobItemCommand.GetParameter(index++).Value = jobItem.ItemId; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.JobId; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.OutputPath; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.Status; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.TargetId; + + _saveJobItemCommand.Transaction = transaction; + + _saveJobItemCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + + private SyncJobItem GetSyncJobItem(IDataReader reader) + { + var info = new SyncJobItem + { + Id = reader.GetGuid(0).ToString("N"), + ItemId = reader.GetString(1), + JobId = reader.GetString(2) + }; + + if (!reader.IsDBNull(3)) + { + info.OutputPath = reader.GetString(3); + } + + if (!reader.IsDBNull(4)) + { + info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader.GetString(4), true); + } + + info.TargetId = reader.GetString(5); + + return info; + } + } +} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 907500ae35..18739ddb32 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 55843bbdbe..5c086c8cc7 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -209,6 +209,7 @@ namespace MediaBrowser.ServerApplication private IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } + private ISyncRepository SyncRepository { get; set; } /// /// Initializes a new instance of the class. @@ -525,6 +526,9 @@ namespace MediaBrowser.ServerApplication AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); RegisterSingleInstance(AuthenticationRepository); + SyncRepository = await GetSyncRepository().ConfigureAwait(false); + RegisterSingleInstance(SyncRepository); + UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer); RegisterSingleInstance(UserManager); @@ -561,7 +565,7 @@ namespace MediaBrowser.ServerApplication ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder); RegisterSingleInstance(ImageProcessor); - SyncManager = new SyncManager(); + SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager")); RegisterSingleInstance(SyncManager); DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager); @@ -706,6 +710,15 @@ namespace MediaBrowser.ServerApplication return repo; } + private async Task GetSyncRepository() + { + var repo = new SyncRepository(LogManager.GetLogger("SyncRepository"), ServerConfigurationManager.ApplicationPaths); + + await repo.Initialize().ConfigureAwait(false); + + return repo; + } + /// /// Configures the repositories. /// diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs index a93ed91960..2642ffde73 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg { EncoderPath = encoder, ProbePath = probe, - Version = Path.GetFileNameWithoutExtension(Path.GetDirectoryName(probe)) + Version = Path.GetFileName(Path.GetDirectoryName(probe)) }; } } diff --git a/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs b/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs index 768e4ee5c6..af24609e8c 100644 --- a/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs +++ b/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs @@ -38,6 +38,8 @@ namespace MediaBrowser.Tests.Resolvers public void TestMultiPartFolders() { Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"blah blah")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"d:\\music\weezer\\03 Pinkerton")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"d:\\music\\michael jackson\\Bad (2012 Remaster)")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc1")); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 92487959db..9235beacf5 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -548,6 +548,7 @@ namespace MediaBrowser.WebDashboard.Api "channelsettings.js", "dashboardgeneral.js", "dashboardpage.js", + "dashboardsync.js", "directorybrowser.js", "dlnaprofile.js", "dlnaprofiles.js", @@ -676,6 +677,7 @@ namespace MediaBrowser.WebDashboard.Api "librarybrowser.css", "detailtable.css", "posteritem.css", + "card.css", "tileitem.css", "metadataeditor.css", "notifications.css", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 25cff11cfa..73174dacf8 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -287,9 +287,15 @@ PreserveNewest + + PreserveNewest + PreserveNewest + + PreserveNewest + PreserveNewest @@ -602,6 +608,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -2074,6 +2083,9 @@ + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs b/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs index a45a449044..6071db6b5a 100644 --- a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs +++ b/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs @@ -88,7 +88,7 @@ namespace MediaBrowser.XbmcMetadata.Images if (item is Episode) { var seasonFolder = Path.GetDirectoryName(item.Path); - + var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; return new[] { Path.Combine(seasonFolder, imageFilename) }; diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index d51c44ad4e..c4edfb4612 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -35,18 +35,18 @@ namespace MediaBrowser.XbmcMetadata.Providers protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - var path = GetMovieSavePath(info); + var path = GetMovieSavePath(info, FileSystem); return directoryService.GetFile(path); } - public static string GetMovieSavePath(ItemInfo item) + public static string GetMovieSavePath(ItemInfo item, IFileSystem fileSystem) { if (Directory.Exists(item.Path)) { var path = item.Path; - return Path.Combine(path, Path.GetFileNameWithoutExtension(path) + ".nfo"); + return Path.Combine(path, fileSystem.GetFileNameWithoutExtension(path) + ".nfo"); } return Path.ChangeExtension(item.Path, ".nfo"); diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index b23473295c..210c743bf3 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -21,10 +21,10 @@ namespace MediaBrowser.XbmcMetadata.Savers public override string GetSavePath(IHasMetadata item) { - return GetMovieSavePath(item); + return GetMovieSavePath(item, FileSystem); } - public static string GetMovieSavePath(IHasMetadata item) + public static string GetMovieSavePath(IHasMetadata item, IFileSystem fileSystem) { var video = (Video)item; @@ -32,7 +32,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { var path = item.ContainingFolderPath; - return Path.Combine(path, Path.GetFileNameWithoutExtension(path) + ".nfo"); + return Path.Combine(path, fileSystem.GetFileNameWithoutExtension(path) + ".nfo"); } return Path.ChangeExtension(item.Path, ".nfo"); -- cgit v1.2.3 From 51e964dae394ae4a211482d562dbc28e79c00c3c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 29 Jul 2014 23:31:35 -0400 Subject: support channels with dlna --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 2 +- .../Playback/Progressive/VideoService.cs | 2 +- .../ScheduledTasks/Tasks/ReloadLoggerFileTask.cs | 2 +- MediaBrowser.Common/Net/IServerManager.cs | 6 - .../Channels/IChannelManager.cs | 16 ++ .../Entities/BasePluginFolder.cs | 2 +- MediaBrowser.Controller/Entities/UserView.cs | 31 +++ MediaBrowser.Controller/Library/TVUtils.cs | 89 ++++++++- MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs | 23 +-- .../ContentDirectory/ContentDirectory.cs | 11 +- .../ContentDirectory/ControlHandler.cs | 220 +++++++++++++++------ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 10 +- MediaBrowser.Dlna/Images/logo120.jpg | Bin 6745 -> 8879 bytes MediaBrowser.Dlna/Images/logo120.png | Bin 4124 -> 13621 bytes MediaBrowser.Dlna/Images/logo240.jpg | Bin 0 -> 22073 bytes MediaBrowser.Dlna/Images/logo240.png | Bin 0 -> 33252 bytes MediaBrowser.Dlna/Images/logo48.jpg | Bin 2484 -> 2997 bytes MediaBrowser.Dlna/Images/logo48.png | Bin 1661 -> 3837 bytes MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 4 + MediaBrowser.Dlna/PlayTo/Device.cs | 4 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 67 ++++--- MediaBrowser.Dlna/Profiles/DefaultProfile.cs | 2 + MediaBrowser.Dlna/Profiles/Xml/Android.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Default.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml | 2 +- .../Profiles/Xml/Sony Blu-ray Player 2013.xml | 2 +- .../Profiles/Xml/Sony Blu-ray Player.xml | 2 +- .../Profiles/Xml/Sony Bravia (2010).xml | 2 +- .../Profiles/Xml/Sony Bravia (2011).xml | 2 +- .../Profiles/Xml/Sony Bravia (2012).xml | 2 +- .../Profiles/Xml/Sony Bravia (2013).xml | 2 +- .../Profiles/Xml/Sony PlayStation 3.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Windows 8 RT.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Windows Phone.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml | 2 +- MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs | 18 ++ MediaBrowser.Dlna/Ssdp/SsdpHandler.cs | 9 +- MediaBrowser.Model/Dlna/SortCriteria.cs | 8 +- .../Channels/ChannelDownloadScheduledTask.cs | 2 +- .../Channels/ChannelManager.cs | 67 ++++++- .../EntryPoints/ExternalPortForwarding.cs | 5 - .../EntryPoints/UdpServerEntryPoint.cs | 7 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 43 ++-- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 8 +- .../Library/Resolvers/TV/SeriesResolver.cs | 18 +- .../Library/UserViewManager.cs | 42 +++- .../LiveTv/CleanDatabaseScheduledTask.cs | 2 +- .../Localization/JavaScript/javascript.json | 1 - .../Localization/JavaScript/kk.json | 8 +- .../Localization/JavaScript/pt_PT.json | 2 +- .../Localization/JavaScript/ru.json | 6 +- .../Localization/Server/ar.json | 12 +- .../Localization/Server/ca.json | 12 +- .../Localization/Server/cs.json | 12 +- .../Localization/Server/da.json | 12 +- .../Localization/Server/de.json | 12 +- .../Localization/Server/el.json | 12 +- .../Localization/Server/en_GB.json | 12 +- .../Localization/Server/en_US.json | 12 +- .../Localization/Server/es.json | 12 +- .../Localization/Server/es_MX.json | 14 +- .../Localization/Server/fr.json | 12 +- .../Localization/Server/he.json | 12 +- .../Localization/Server/it.json | 12 +- .../Localization/Server/kk.json | 26 ++- .../Localization/Server/ko.json | 12 +- .../Localization/Server/ms.json | 12 +- .../Localization/Server/nb.json | 12 +- .../Localization/Server/nl.json | 46 +++-- .../Localization/Server/pl.json | 12 +- .../Localization/Server/pt_BR.json | 22 ++- .../Localization/Server/pt_PT.json | 44 +++-- .../Localization/Server/ru.json | 28 ++- .../Localization/Server/sv.json | 12 +- .../Localization/Server/vi.json | 12 +- .../Localization/Server/zh_TW.json | 12 +- .../ServerManager/ServerManager.cs | 9 - .../Session/WebSocketController.cs | 25 +++ .../Udp/UdpServer.cs | 14 +- .../Native/ServerAuthorization.cs | 2 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 14 +- .../FFMpeg/FFMpegDownloadInfo.cs | 8 +- .../FFMpeg/FFMpegDownloader.cs | 8 +- MediaBrowser.Tests/Resolvers/TvUtilTests.cs | 2 + 93 files changed, 951 insertions(+), 320 deletions(-) create mode 100644 MediaBrowser.Dlna/Images/logo240.jpg create mode 100644 MediaBrowser.Dlna/Images/logo240.png (limited to 'MediaBrowser.Controller/Library') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 5cae4c3f7f..df85ca3cbc 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -626,7 +626,7 @@ namespace MediaBrowser.Api.Playback /// The state. /// The output video codec. /// System.String. - protected string GetInternalGraphicalSubtitleParam(StreamState state, string outputVideoCodec) + protected string GetGraphicalSubtitleParam(StreamState state, string outputVideoCodec) { var outputSizeParam = string.Empty; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index fc1c627e94..ccaa918ec7 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -522,7 +522,7 @@ namespace MediaBrowser.Api.Playback.Hls // This is for internal graphical subs if (hasGraphicalSubs) { - args += GetInternalGraphicalSubtitleParam(state, codec); + args += GetGraphicalSubtitleParam(state, codec); } return args; diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 28c0219fce..1dffa54114 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -186,7 +186,7 @@ namespace MediaBrowser.Api.Playback.Hls // This is for internal graphical subs if (hasGraphicalSubs) { - args += GetInternalGraphicalSubtitleParam(state, codec); + args += GetGraphicalSubtitleParam(state, codec); } return args; diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index bedacc0d29..cdaa99130b 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback.Progressive // This is for internal graphical subs if (hasGraphicalSubs) { - args += GetInternalGraphicalSubtitleParam(state, codec); + args += GetGraphicalSubtitleParam(state, codec); } return args; diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs index 38b6b4ad68..78f60632fa 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks public bool IsHidden { - get { return false; } + get { return true; } } public bool IsEnabled diff --git a/MediaBrowser.Common/Net/IServerManager.cs b/MediaBrowser.Common/Net/IServerManager.cs index f6ac0ab68a..84e5785799 100644 --- a/MediaBrowser.Common/Net/IServerManager.cs +++ b/MediaBrowser.Common/Net/IServerManager.cs @@ -10,12 +10,6 @@ namespace MediaBrowser.Common.Net /// public interface IServerManager : IDisposable { - /// - /// Gets the web socket port number. - /// - /// The web socket port number. - int WebSocketPortNumber { get; } - /// /// Starts this instance. /// diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 744eab96e0..252e2aee53 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -43,6 +43,14 @@ namespace MediaBrowser.Controller.Channels /// Channel. Channel GetChannel(string id); + /// + /// Gets the channels internal. + /// + /// The query. + /// The cancellation token. + /// Task<QueryResult<Channel>>. + Task> GetChannelsInternal(ChannelQuery query, CancellationToken cancellationToken); + /// /// Gets the channels. /// @@ -75,6 +83,14 @@ namespace MediaBrowser.Controller.Channels /// Task{QueryResult{BaseItemDto}}. Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken); + /// + /// Gets the channel items internal. + /// + /// The query. + /// The cancellation token. + /// Task<QueryResult<BaseItem>>. + Task> GetChannelItemsInternal(ChannelItemQuery query, CancellationToken cancellationToken); + /// /// Gets the cached channel item media sources. /// diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index e1383923f6..fa2b49a60b 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities public virtual string CollectionType { - get { return Model.Entities.CollectionType.BoxSets; } + get { return null; } } } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 619a497f5f..be426d99eb 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Entities; +using MoreLinq; using System; using System.Collections.Generic; using System.Linq; @@ -29,6 +30,10 @@ namespace MediaBrowser.Controller.Entities case CollectionType.Trailers: return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) .OfType(); + case CollectionType.Movies: + return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) + .Where(i => i is Movie || i is BoxSet) + .DistinctBy(i => i.Id); default: return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren)); } @@ -70,4 +75,30 @@ namespace MediaBrowser.Controller.Entities return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty); } } + + public class SpecialFolder : Folder + { + public SpecialFolderType SpecialFolderType { get; set; } + public string ItemTypeName { get; set; } + public string ParentId { get; set; } + + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) + { + var parent = (Folder)LibraryManager.GetItemById(new Guid(ParentId)); + + if (SpecialFolderType == SpecialFolderType.ItemsByType) + { + var items = parent.GetRecursiveChildren(user, includeLinkedChildren); + + return items.Where(i => string.Equals(i.GetType().Name, ItemTypeName, StringComparison.OrdinalIgnoreCase)); + } + + return new List(); + } + } + + public enum SpecialFolderType + { + ItemsByType = 1 + } } diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 86699272e8..6ef1fb6f72 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Controller.Library { @@ -136,6 +137,16 @@ namespace MediaBrowser.Controller.Library return val; } + if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase)) + { + var testFilename = filename.Substring(1); + + if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + return val; + } + } + // Look for one of the season folder names foreach (var name in SeasonFolderNames) { @@ -182,7 +193,7 @@ namespace MediaBrowser.Controller.Library return null; } - return int.Parse(path.Substring(numericStart, length)); + return int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture); } /// @@ -194,21 +205,59 @@ namespace MediaBrowser.Controller.Library /// true if [is season folder] [the specified path]; otherwise, false. private static bool IsSeasonFolder(string path, IDirectoryService directoryService, IFileSystem fileSystem) { + var seasonNumber = GetSeasonNumberFromPath(path); + var hasSeasonNumber = seasonNumber != null; + + if (!hasSeasonNumber) + { + return false; + } + // It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3 - return GetSeasonNumberFromPath(path) != null && - !directoryService.GetFiles(path) - .Any(i => EntityResolutionHelper.IsAudioFile(i.FullName) && !string.Equals(fileSystem.GetFileNameWithoutExtension(i), BaseItem.ThemeSongFilename)); + foreach (var fileSystemInfo in directoryService.GetFileSystemEntries(path)) + { + var attributes = fileSystemInfo.Attributes; + + if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + continue; + } + + if ((attributes & FileAttributes.System) == FileAttributes.System) + { + continue; + } + + if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + //if (IsBadFolder(fileSystemInfo.Name)) + //{ + // return false; + //} + } + else + { + if (EntityResolutionHelper.IsAudioFile(fileSystemInfo.FullName) && + !string.Equals(fileSystem.GetFileNameWithoutExtension(fileSystemInfo), BaseItem.ThemeSongFilename)) + { + return false; + } + } + } + + return true; } /// /// Determines whether [is series folder] [the specified path]. /// /// The path. - /// if set to true [consider seasonless series]. + /// if set to true [consider seasonless entries]. /// The file system children. /// The directory service. + /// The file system. /// true if [is series folder] [the specified path]; otherwise, false. - public static bool IsSeriesFolder(string path, bool considerSeasonlessSeries, IEnumerable fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem) + public static bool IsSeriesFolder(string path, bool considerSeasonlessEntries, IEnumerable fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, ILogger logger) { // A folder with more than 3 non-season folders in will not becounted as a series var nonSeriesFolders = 0; @@ -231,15 +280,20 @@ namespace MediaBrowser.Controller.Library { if (IsSeasonFolder(child.FullName, directoryService, fileSystem)) { + logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName); return true; } - if (!EntityResolutionHelper.IgnoreFolders.Contains(child.Name, StringComparer.OrdinalIgnoreCase)) + + if (IsBadFolder(child.Name)) { + logger.Debug("Invalid folder under series: {0}", child.FullName); + nonSeriesFolders++; } if (nonSeriesFolders >= 3) { + logger.Debug("{0} not a series due to 3 or more invalid folders.", path); return false; } } @@ -249,7 +303,7 @@ namespace MediaBrowser.Controller.Library if (EntityResolutionHelper.IsVideoFile(fullName) || EntityResolutionHelper.IsVideoPlaceHolder(fullName)) { - if (GetEpisodeNumberFromFile(fullName, considerSeasonlessSeries).HasValue) + if (GetEpisodeNumberFromFile(fullName, considerSeasonlessEntries).HasValue) { return true; } @@ -257,9 +311,28 @@ namespace MediaBrowser.Controller.Library } } + logger.Debug("{0} is not a series folder.", path); return false; } + private static bool IsBadFolder(string name) + { + if (string.Equals(name, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(name, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return !EntityResolutionHelper.IgnoreFolders.Contains(name, StringComparer.OrdinalIgnoreCase); + } + /// /// Episodes the number from file. /// diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index cc5cb1b1a2..78db68f63f 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Dlna.Channels _localServersLookup = localServersLookup; _deviceDiscovery = deviceDiscovery; - deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; + //deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; } @@ -196,25 +196,16 @@ namespace MediaBrowser.Dlna.Channels public class ServerChannel : IChannel, IFactoryChannel { - private readonly List _servers = new List(); private readonly IHttpClient _httpClient; private readonly ILogger _logger; - private readonly string _controlUrl; + public string ControlUrl { get; set; } + public List Servers { get; set; } - /// - /// Prevents core from throwing an exception - /// - public ServerChannel() + public ServerChannel(IHttpClient httpClient, ILogger logger) { - - } - - public ServerChannel(List servers, IHttpClient httpClient, ILogger logger, string controlUrl) - { - _servers = servers; _httpClient = httpClient; _logger = logger; - _controlUrl = controlUrl; + Servers = new List(); } public string Name @@ -272,7 +263,7 @@ namespace MediaBrowser.Dlna.Channels if (string.IsNullOrWhiteSpace(query.FolderId)) { - items = _servers.Select(i => new ChannelItemInfo + items = Servers.Select(i => new ChannelItemInfo { FolderType = ChannelFolderType.Container, Id = GetServerId(i), @@ -291,7 +282,7 @@ namespace MediaBrowser.Dlna.Channels Limit = query.Limit, StartIndex = query.StartIndex, ParentId = folderId, - ContentDirectoryUrl = _controlUrl + ContentDirectoryUrl = ControlUrl }, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs index f594b4471d..f5731b8938 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -21,6 +22,8 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly IDlnaManager _dlna; private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; + private readonly IUserViewManager _userViewManager; + private readonly IChannelManager _channelManager; public ContentDirectory(IDlnaManager dlna, IUserDataManager userDataManager, @@ -29,7 +32,7 @@ namespace MediaBrowser.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient) + IHttpClient httpClient, IUserViewManager userViewManager, IChannelManager channelManager) : base(logger, httpClient) { _dlna = dlna; @@ -38,6 +41,8 @@ namespace MediaBrowser.Dlna.ContentDirectory _libraryManager = libraryManager; _config = config; _userManager = userManager; + _userViewManager = userViewManager; + _channelManager = channelManager; } private int SystemUpdateId @@ -73,7 +78,9 @@ namespace MediaBrowser.Dlna.ContentDirectory _userDataManager, user, SystemUpdateId, - _config) + _config, + _userViewManager, + _channelManager) .ProcessControlRequest(request); } diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index b4f918e68b..1553435aeb 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -9,8 +10,10 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Dlna.Didl; using MediaBrowser.Dlna.Server; using MediaBrowser.Dlna.Service; +using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using System; @@ -19,6 +22,7 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml; namespace MediaBrowser.Dlna.ContentDirectory @@ -40,14 +44,18 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly DidlBuilder _didlBuilder; private readonly DeviceProfile _profile; + private readonly IUserViewManager _userViewManager; + private readonly IChannelManager _channelManager; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config) + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, IUserViewManager userViewManager, IChannelManager channelManager) : base(config, logger) { _libraryManager = libraryManager; _userDataManager = userDataManager; _user = user; _systemUpdateId = systemUpdateId; + _userViewManager = userViewManager; + _channelManager = channelManager; _profile = profile; _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress); @@ -69,7 +77,7 @@ namespace MediaBrowser.Dlna.ContentDirectory return HandleGetSystemUpdateID(); if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase)) - return HandleBrowse(methodParams, user, deviceId); + return HandleBrowse(methodParams, user, deviceId).Result; if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase)) return HandleXGetFeatureList(); @@ -78,7 +86,7 @@ namespace MediaBrowser.Dlna.ContentDirectory return HandleXSetBookmark(methodParams, user); if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase)) - return HandleSearch(methodParams, user, deviceId); + return HandleSearch(methodParams, user, deviceId).Result; throw new ResourceNotFoundException("Unexpected control request name: " + methodName); } @@ -141,7 +149,7 @@ namespace MediaBrowser.Dlna.ContentDirectory return builder.ToString(); } - private IEnumerable> HandleBrowse(Headers sparams, User user, string deviceId) + private async Task>> HandleBrowse(Headers sparams, User user, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; @@ -149,16 +157,20 @@ namespace MediaBrowser.Dlna.ContentDirectory var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); var provided = 0; - var requested = 0; - var start = 0; - if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0) + int? requested = 0; + int? start = 0; + + int requestedVal; + if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0) { - requested = 0; + requested = requestedVal; } - if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0) + + int startVal; + if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0) { - start = 0; + start = startVal; } //var root = GetItem(id) as IMediaFolder; @@ -173,34 +185,26 @@ namespace MediaBrowser.Dlna.ContentDirectory var folder = (Folder)GetItemFromObjectId(id, user); - var children = GetChildrenSorted(folder, user, sortCriteria).ToList(); + var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); - var totalCount = children.Count; + var totalCount = childrenResult.TotalRecordCount; if (string.Equals(flag, "BrowseMetadata")) { - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, children.Count, filter)); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter)); provided++; } else { - if (start > 0) - { - children = children.Skip(start).ToList(); - } - if (requested > 0) - { - children = children.Take(requested).ToList(); - } - - provided = children.Count; + provided = childrenResult.Items.Length; - foreach (var i in children) + foreach (var i in childrenResult.Items) { if (i.IsFolder) { var f = (Folder)i; - var childCount = GetChildrenSorted(f, user, sortCriteria).Count(); + var childCount = (await GetChildrenSorted(f, user, sortCriteria, null, 0).ConfigureAwait(false)) + .TotalRecordCount; result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); } @@ -222,7 +226,7 @@ namespace MediaBrowser.Dlna.ContentDirectory }; } - private IEnumerable> HandleSearch(Headers sparams, User user, string deviceId) + private async Task>> HandleSearch(Headers sparams, User user, string deviceId) { var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); @@ -230,16 +234,19 @@ namespace MediaBrowser.Dlna.ContentDirectory // sort example: dc:title, dc:date - var requested = 0; - var start = 0; + int? requested = 0; + int? start = 0; - if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0) + int requestedVal; + if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0) { - requested = 0; + requested = requestedVal; } - if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0) + + int startVal; + if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0) { - start = 0; + start = startVal; } //var root = GetItem(id) as IMediaFolder; @@ -259,27 +266,19 @@ namespace MediaBrowser.Dlna.ContentDirectory var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user); - var children = GetChildrenSorted(folder, user, searchCriteria, sortCriteria).ToList(); + var childrenResult = (await GetChildrenSorted(folder, user, searchCriteria, sortCriteria, start, requested).ConfigureAwait(false)); - var totalCount = children.Count; + var totalCount = childrenResult.TotalRecordCount; - if (start > 0) - { - children = children.Skip(start).ToList(); - } - if (requested > 0) - { - children = children.Take(requested).ToList(); - } - - var provided = children.Count; + var provided = childrenResult.Items.Length; - foreach (var i in children) + foreach (var i in childrenResult.Items) { if (i.IsFolder) { var f = (Folder)i; - var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count(); + var childCount = (await GetChildrenSorted(f, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false)) + .TotalRecordCount; result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); } @@ -300,15 +299,16 @@ namespace MediaBrowser.Dlna.ContentDirectory }; } - private IEnumerable GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort) + private async Task> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { if (search.SearchType == SearchType.Unknown) { - return GetChildrenSorted(folder, user, sort); + return await GetChildrenSorted(folder, user, sort, startIndex, limit).ConfigureAwait(false); } - var items = folder.GetRecursiveChildren(user); - items = FilterUnsupportedContent(items); + var result = await GetChildrenSorted(folder, user, sort, null, null).ConfigureAwait(false); + + var items = FilterUnsupportedContent(result.Items); if (search.SearchType == SearchType.Audio) { @@ -324,12 +324,123 @@ namespace MediaBrowser.Dlna.ContentDirectory } else if (search.SearchType == SearchType.Playlist) { + } - return SortItems(items, user, sort); + items = SortItems(items, user, sort); + + return ToResult(items, startIndex, limit); + } + + private async Task> GetChildrenSorted(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit) + { + if (folder is UserRootFolder) + { + var result = await _userViewManager.GetUserViews(new UserViewQuery + { + UserId = user.Id.ToString("N") + + }, CancellationToken.None).ConfigureAwait(false); + + return ToResult(result, startIndex, limit); + } + + var view = folder as UserView; + + if (view != null) + { + var result = await GetUserViewChildren(view, user, sort).ConfigureAwait(false); + + return ToResult(result, startIndex, limit); + } + + var channel = folder as Channel; + + if (channel != null) + { + return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery + { + ChannelId = channel.Id.ToString("N"), + Limit = limit, + StartIndex = startIndex, + UserId = user.Id.ToString("N") + + }, CancellationToken.None); + } + + var channelFolderItem = folder as ChannelFolderItem; + + if (channelFolderItem != null) + { + return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery + { + ChannelId = channelFolderItem.ChannelId, + FolderId = channelFolderItem.Id.ToString("N"), + Limit = limit, + StartIndex = startIndex, + UserId = user.Id.ToString("N") + + }, CancellationToken.None); + } + + return ToResult(GetPlainFolderChildrenSorted(folder, user, sort), startIndex, limit); + } + + private QueryResult ToResult(IEnumerable items, int? startIndex, int? limit) + { + var list = items.ToArray(); + var totalCount = list.Length; + + if (startIndex.HasValue) + { + list = list.Skip(startIndex.Value).ToArray(); + } + + if (limit.HasValue) + { + list = list.Take(limit.Value).ToArray(); + } + + return new QueryResult + { + Items = list, + TotalRecordCount = totalCount + }; + } + + private async Task> GetUserViewChildren(UserView folder, User user, SortCriteria sort) + { + if (string.Equals(folder.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) + { + return new List(); + } + if (string.Equals(folder.ViewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase)) + { + var result = await _channelManager.GetChannelsInternal(new ChannelQuery() + { + UserId = user.Id.ToString("N") + + }, CancellationToken.None).ConfigureAwait(false); + + return result.Items; + } + if (string.Equals(folder.ViewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + return SortItems(folder.GetChildren(user, true).OfType(), user, sort); + } + if (string.Equals(folder.ViewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) + { + return GetPlainFolderChildrenSorted(folder, user, sort); + } + if (string.Equals(folder.ViewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) + { + return SortItems(folder.GetChildren(user, true).OfType(), user, sort); + } + + return GetPlainFolderChildrenSorted(folder, user, sort); } - private IEnumerable GetChildrenSorted(Folder folder, User user, SortCriteria sort) + private IEnumerable GetPlainFolderChildrenSorted(Folder folder, User user, SortCriteria sort) { var items = folder.GetChildren(user, true); @@ -345,7 +456,7 @@ namespace MediaBrowser.Dlna.ContentDirectory private IEnumerable SortItems(IEnumerable items, User user, SortCriteria sort) { - return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending); + return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, sort.SortOrder); } private IEnumerable FilterUnsupportedContent(IEnumerable items) @@ -353,14 +464,12 @@ namespace MediaBrowser.Dlna.ContentDirectory return items.Where(i => { // Unplayable - // TODO: Display and prevent playback with restricted flag? if (i.LocationType == LocationType.Virtual) { return false; } // Unplayable - // TODO: Display and prevent playback with restricted flag? var supportsPlaceHolder = i as ISupportsPlaceHolders; if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder) { @@ -368,7 +477,6 @@ namespace MediaBrowser.Dlna.ContentDirectory } // Upnp renderers won't understand these - // TODO: Display and prevent playback with restricted flag? if (i is Game || i is Book) { return false; diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 739793c032..649ba2c8ff 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -612,6 +613,13 @@ namespace MediaBrowser.Dlna.Didl { return GetImageInfo(item, ImageType.Thumb); } + if (item.HasImage(ImageType.Backdrop)) + { + if (item is Channel) + { + return GetImageInfo(item, ImageType.Backdrop); + } + } if (item is Audio || item is Episode) { @@ -633,7 +641,7 @@ namespace MediaBrowser.Dlna.Didl try { - tag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); + tag = _imageProcessor.GetImageCacheTag(item, type); } catch { diff --git a/MediaBrowser.Dlna/Images/logo120.jpg b/MediaBrowser.Dlna/Images/logo120.jpg index 1de803c8fa..394c8e1376 100644 Binary files a/MediaBrowser.Dlna/Images/logo120.jpg and b/MediaBrowser.Dlna/Images/logo120.jpg differ diff --git a/MediaBrowser.Dlna/Images/logo120.png b/MediaBrowser.Dlna/Images/logo120.png index 2dd04d4681..97bdef818b 100644 Binary files a/MediaBrowser.Dlna/Images/logo120.png and b/MediaBrowser.Dlna/Images/logo120.png differ diff --git a/MediaBrowser.Dlna/Images/logo240.jpg b/MediaBrowser.Dlna/Images/logo240.jpg new file mode 100644 index 0000000000..c805523516 Binary files /dev/null and b/MediaBrowser.Dlna/Images/logo240.jpg differ diff --git a/MediaBrowser.Dlna/Images/logo240.png b/MediaBrowser.Dlna/Images/logo240.png new file mode 100644 index 0000000000..532f123178 Binary files /dev/null and b/MediaBrowser.Dlna/Images/logo240.png differ diff --git a/MediaBrowser.Dlna/Images/logo48.jpg b/MediaBrowser.Dlna/Images/logo48.jpg index f1e7302aae..52b9853541 100644 Binary files a/MediaBrowser.Dlna/Images/logo48.jpg and b/MediaBrowser.Dlna/Images/logo48.jpg differ diff --git a/MediaBrowser.Dlna/Images/logo48.png b/MediaBrowser.Dlna/Images/logo48.png index 3b13d141ce..29d4a05289 100644 Binary files a/MediaBrowser.Dlna/Images/logo48.png and b/MediaBrowser.Dlna/Images/logo48.png differ diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 461470b7ac..962c2a211e 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -184,6 +184,10 @@ + + + + - + \ No newline at end of file diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs new file mode 100644 index 0000000000..e6b525e534 --- /dev/null +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -0,0 +1,116 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// Class SessionInfoWebSocketListener + /// + class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "Sessions"; } + } + + /// + /// The _kernel + /// + private readonly ISessionManager _sessionManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The session manager. + public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) + : base(logger) + { + _sessionManager = sessionManager; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity += _sessionManager_SessionActivity; + } + + void _sessionManager_SessionActivity(object sender, SessionEventArgs e) + { + SendData(false); + } + + void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) + { + SendData(false); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + SendData(true); + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task> GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); + } + + protected override bool SendOnTimer + { + get + { + return false; + } + } + + protected override void Dispose(bool dispose) + { + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity -= _sessionManager_SessionActivity; + + base.Dispose(dispose); + } + } +} diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs new file mode 100644 index 0000000000..e2c95eba9c --- /dev/null +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -0,0 +1,506 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// Class GetSessions + /// + [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] + [Authenticated] + public class GetSessions : IReturn> + { + [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? ControllableByUserId { get; set; } + + [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + } + + /// + /// Class DisplayContent + /// + [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] + [Authenticated] + public class DisplayContent : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemType { get; set; } + + /// + /// Artist name, genre name, item Id, etc + /// + /// The item identifier. + [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemId { get; set; } + + /// + /// Gets or sets the name of the item. + /// + /// The name of the item. + [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemName { get; set; } + } + + [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] + [Authenticated] + public class Play : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string ItemIds { get; set; } + + /// + /// Gets or sets the start position ticks that the first item should be played at + /// + /// The start position ticks. + [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? StartPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public PlayCommand PlayCommand { get; set; } + } + + [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] + [Authenticated] + public class SendPlaystateCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the position to seek to + /// + [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? SeekPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public PlaystateCommand Command { get; set; } + } + + [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendSystemCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendGeneralCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendFullGeneralCommand : GeneralCommand, IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + } + + [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] + [Authenticated] + public class SendMessageCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Text { get; set; } + + [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Header { get; set; } + + [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? TimeoutMs { get; set; } + } + + [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] + [Authenticated] + public class AddUserToSession : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] + [Authenticated] + public class RemoveUserFromSession : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] + public class PostCapabilities : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayableMediaTypes { get; set; } + + [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string SupportedCommands { get; set; } + + [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MessageCallbackUrl { get; set; } + + [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsMediaControl { get; set; } + } + + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + + [Route("/Auth/Keys", "GET")] + public class GetApiKeys + { + } + + [Route("/Auth/Keys/{Key}", "DELETE")] + public class RevokeKey + { + [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Key { get; set; } + } + + [Route("/Auth/Keys", "POST")] + public class CreateKey + { + [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string App { get; set; } + } + + /// + /// Class SessionsService + /// + public class SessionsService : BaseApiService + { + /// + /// The _session manager + /// + private readonly ISessionManager _sessionManager; + + private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; + private readonly IAuthenticationRepository _authRepo; + + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The user manager. + /// The authentication context. + /// The authentication repo. + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) + { + _sessionManager = sessionManager; + _userManager = userManager; + _authContext = authContext; + _authRepo = authRepo; + } + + public void Delete(RevokeKey request) + { + var task = _sessionManager.RevokeToken(request.Key); + + Task.WaitAll(task); + } + + public void Post(CreateKey request) + { + var task = _authRepo.Create(new AuthenticationInfo + { + AppName = request.App, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N"), + DateCreated = DateTime.UtcNow + + }, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); + } + + public object Get(GetApiKeys request) + { + var result = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true + }); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSessions request) + { + var result = _sessionManager.Sessions.Where(i => i.IsActive); + + if (!string.IsNullOrEmpty(request.DeviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (request.ControllableByUserId.HasValue) + { + result = result.Where(i => i.SupportsMediaControl); + + var user = _userManager.GetUserById(request.ControllableByUserId.Value); + + if (!user.Configuration.EnableRemoteControlOfOtherUsers) + { + result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); + } + } + + return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); + } + + public void Post(SendPlaystateCommand request) + { + var command = new PlaystateRequest + { + Command = request.Command, + SeekPositionTicks = request.SeekPositionTicks + }; + + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(DisplayContent request) + { + var command = new BrowseRequest + { + ItemId = request.ItemId, + ItemName = request.ItemName, + ItemType = request.ItemType + }; + + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendSystemCommand request) + { + GeneralCommandType commandType; + + if (Enum.TryParse(request.Command, true, out commandType)) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = commandType.ToString(), + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendMessageCommand request) + { + var command = new MessageCommand + { + Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, + TimeoutMs = request.TimeoutMs, + Text = request.Text + }; + + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(Play request) + { + var command = new PlayRequest + { + ItemIds = request.ItemIds.Split(',').ToArray(), + + PlayCommand = request.PlayCommand, + StartPositionTicks = request.StartPositionTicks + }; + + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendGeneralCommand request) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = request.Command, + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendFullGeneralCommand request) + { + var currentSession = GetSession(); + + request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(AddUserToSession request) + { + _sessionManager.AddAdditionalUser(request.Id, request.UserId); + } + + public void Delete(RemoveUserFromSession request) + { + _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); + } + + public void Post(PostCapabilities request) + { + if (string.IsNullOrWhiteSpace(request.Id)) + { + request.Id = GetSession().Id; + } + _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities + { + PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportsMediaControl = request.SupportsMediaControl, + + MessageCallbackUrl = request.MessageCallbackUrl + }); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs deleted file mode 100644 index 8017f35231..0000000000 --- a/MediaBrowser.Api/SessionsService.cs +++ /dev/null @@ -1,506 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Session; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - /// - /// Class GetSessions - /// - [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] - [Authenticated] - public class GetSessions : IReturn> - { - [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid? ControllableByUserId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - } - - /// - /// Class DisplayContent - /// - [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] - [Authenticated] - public class DisplayContent : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemType { get; set; } - - /// - /// Artist name, genre name, item Id, etc - /// - /// The item identifier. - [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - /// - /// Gets or sets the name of the item. - /// - /// The name of the item. - [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemName { get; set; } - } - - [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] - [Authenticated] - public class Play : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string ItemIds { get; set; } - - /// - /// Gets or sets the start position ticks that the first item should be played at - /// - /// The start position ticks. - [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? StartPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayCommand PlayCommand { get; set; } - } - - [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] - [Authenticated] - public class SendPlaystateCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the position to seek to - /// - [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? SeekPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public PlaystateCommand Command { get; set; } - } - - [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendSystemCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendGeneralCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendFullGeneralCommand : GeneralCommand, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] - [Authenticated] - public class SendMessageCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Text { get; set; } - - [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Header { get; set; } - - [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? TimeoutMs { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] - [Authenticated] - public class AddUserToSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] - [Authenticated] - public class RemoveUserFromSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Authenticated] - public class PostCapabilities : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayableMediaTypes { get; set; } - - [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SupportedCommands { get; set; } - - [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MessageCallbackUrl { get; set; } - - [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsMediaControl { get; set; } - } - - [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] - public class ReportSessionEnded : IReturnVoid - { - } - - [Route("/Auth/Keys", "GET")] - public class GetApiKeys - { - } - - [Route("/Auth/Keys/{Key}", "DELETE")] - public class RevokeKey - { - [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Key { get; set; } - } - - [Route("/Auth/Keys", "POST")] - public class CreateKey - { - [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string App { get; set; } - } - - /// - /// Class SessionsService - /// - public class SessionsService : BaseApiService - { - /// - /// The _session manager - /// - private readonly ISessionManager _sessionManager; - - private readonly IUserManager _userManager; - private readonly IAuthorizationContext _authContext; - private readonly IAuthenticationRepository _authRepo; - - /// - /// Initializes a new instance of the class. - /// - /// The session manager. - /// The user manager. - /// The authentication context. - /// The authentication repo. - public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) - { - _sessionManager = sessionManager; - _userManager = userManager; - _authContext = authContext; - _authRepo = authRepo; - } - - public void Delete(RevokeKey request) - { - var task = _sessionManager.RevokeToken(request.Key); - - Task.WaitAll(task); - } - - public void Post(CreateKey request) - { - var task = _authRepo.Create(new AuthenticationInfo - { - AppName = request.App, - IsActive = true, - AccessToken = Guid.NewGuid().ToString("N"), - DateCreated = DateTime.UtcNow - - }, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(ReportSessionEnded request) - { - var auth = _authContext.GetAuthorizationInfo(Request); - - _sessionManager.Logout(auth.Token); - } - - public object Get(GetApiKeys request) - { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - IsActive = true - }); - - return ToOptimizedResult(result); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSessions request) - { - var result = _sessionManager.Sessions.Where(i => i.IsActive); - - if (!string.IsNullOrEmpty(request.DeviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (request.ControllableByUserId.HasValue) - { - result = result.Where(i => i.SupportsMediaControl); - - var user = _userManager.GetUserById(request.ControllableByUserId.Value); - - if (!user.Configuration.EnableRemoteControlOfOtherUsers) - { - result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); - } - } - - return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); - } - - public void Post(SendPlaystateCommand request) - { - var command = new PlaystateRequest - { - Command = request.Command, - SeekPositionTicks = request.SeekPositionTicks - }; - - var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(DisplayContent request) - { - var command = new BrowseRequest - { - ItemId = request.ItemId, - ItemName = request.ItemName, - ItemType = request.ItemType - }; - - var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendSystemCommand request) - { - GeneralCommandType commandType; - - if (Enum.TryParse(request.Command, true, out commandType)) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = commandType.ToString(), - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendMessageCommand request) - { - var command = new MessageCommand - { - Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, - TimeoutMs = request.TimeoutMs, - Text = request.Text - }; - - var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(Play request) - { - var command = new PlayRequest - { - ItemIds = request.ItemIds.Split(',').ToArray(), - - PlayCommand = request.PlayCommand, - StartPositionTicks = request.StartPositionTicks - }; - - var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendGeneralCommand request) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = request.Command, - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendFullGeneralCommand request) - { - var currentSession = GetSession(); - - request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(AddUserToSession request) - { - _sessionManager.AddAdditionalUser(request.Id, request.UserId); - } - - public void Delete(RemoveUserFromSession request) - { - _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); - } - - public void Post(PostCapabilities request) - { - if (string.IsNullOrWhiteSpace(request.Id)) - { - request.Id = GetSession().Id; - } - _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities - { - PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportsMediaControl = request.SupportsMediaControl, - - MessageCallbackUrl = request.MessageCallbackUrl - }); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs new file mode 100644 index 0000000000..0ccc28c6ff --- /dev/null +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using ServiceStack; + +namespace MediaBrowser.Api.System +{ + [Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")] + public class GetActivityLogs : IReturn> + { + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + [Authenticated] + public class ActivityLogService : BaseApiService + { + private readonly IActivityManager _activityManager; + + public ActivityLogService(IActivityManager activityManager) + { + _activityManager = activityManager; + } + + public object Get(GetActivityLogs request) + { + var result = _activityManager.GetActivityLogEntries(request.StartIndex, request.Limit); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs new file mode 100644 index 0000000000..c20cef3b38 --- /dev/null +++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs @@ -0,0 +1,49 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class SystemInfoWebSocketListener + /// + public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "SystemInfo"; } + } + + /// + /// The _kernel + /// + private readonly IServerApplicationHost _appHost; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The app host. + public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) + : base(logger) + { + _appHost = appHost; + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_appHost.GetSystemInfo()); + } + } +} diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs new file mode 100644 index 0000000000..3913275eee --- /dev/null +++ b/MediaBrowser.Api/System/SystemService.cs @@ -0,0 +1,178 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.System; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class GetSystemInfo + /// + [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] + public class GetSystemInfo : IReturn + { + + } + + [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] + public class GetPublicSystemInfo : IReturn + { + + } + + /// + /// Class RestartApplication + /// + [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] + [Authenticated] + public class RestartApplication + { + } + + [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] + [Authenticated] + public class ShutdownApplication + { + } + + [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] + [Authenticated] + public class GetServerLogs : IReturn> + { + } + + [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] + public class GetLogFile + { + [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Name { get; set; } + } + + /// + /// Class SystemInfoService + /// + public class SystemService : BaseApiService + { + /// + /// The _app host + /// + private readonly IServerApplicationHost _appHost; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// The app host. + /// The application paths. + /// The file system. + /// jsonSerializer + public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) + { + _appHost = appHost; + _appPaths = appPaths; + _fileSystem = fileSystem; + } + + public object Get(GetServerLogs request) + { + List files; + + try + { + files = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => string.Equals(i.Extension, ".txt", global::System.StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List(); + } + + var result = files.Select(i => new LogFile + { + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i), + Name = i.Name, + Size = i.Length + + }).OrderByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ThenBy(i => i.Name) + .ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetLogFile request) + { + var file = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .First(i => string.Equals(i.Name, request.Name, global::System.StringComparison.OrdinalIgnoreCase)); + + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + return ToOptimizedResult(result); + } + + public object Get(GetPublicSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + var publicInfo = new PublicSystemInfo + { + Id = result.Id, + ServerName = result.ServerName, + Version = result.Version + }; + + return ToOptimizedResult(publicInfo); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(RestartApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Restart().ConfigureAwait(false); + }); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(ShutdownApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Shutdown().ConfigureAwait(false); + }); + } + + } +} diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs deleted file mode 100644 index 259b1d8921..0000000000 --- a/MediaBrowser.Api/SystemService.cs +++ /dev/null @@ -1,177 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.System; -using ServiceStack; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - /// - /// Class GetSystemInfo - /// - [Route("/System/Info", "GET", Summary = "Gets information about the server")] - [Authenticated] - public class GetSystemInfo : IReturn - { - - } - - [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] - public class GetPublicSystemInfo : IReturn - { - - } - - /// - /// Class RestartApplication - /// - [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] - [Authenticated] - public class RestartApplication - { - } - - [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] - [Authenticated] - public class ShutdownApplication - { - } - - [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] - [Authenticated] - public class GetServerLogs : IReturn> - { - } - - [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] - public class GetLogFile - { - [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Name { get; set; } - } - - /// - /// Class SystemInfoService - /// - public class SystemService : BaseApiService - { - /// - /// The _app host - /// - private readonly IServerApplicationHost _appHost; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The application paths. - /// The file system. - /// jsonSerializer - public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) - { - _appHost = appHost; - _appPaths = appPaths; - _fileSystem = fileSystem; - } - - public object Get(GetServerLogs request) - { - List files; - - try - { - files = new DirectoryInfo(_appPaths.LogDirectoryPath) - .EnumerateFiles("*", SearchOption.AllDirectories) - .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - catch (DirectoryNotFoundException) - { - files = new List(); - } - - var result = files.Select(i => new LogFile - { - DateCreated = _fileSystem.GetCreationTimeUtc(i), - DateModified = _fileSystem.GetLastWriteTimeUtc(i), - Name = i.Name, - Size = i.Length - - }).OrderByDescending(i => i.DateModified) - .ThenByDescending(i => i.DateCreated) - .ThenBy(i => i.Name) - .ToList(); - - return ToOptimizedResult(result); - } - - public object Get(GetLogFile request) - { - var file = new DirectoryInfo(_appPaths.LogDirectoryPath) - .EnumerateFiles("*", SearchOption.AllDirectories) - .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase)); - - return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - return ToOptimizedResult(result); - } - - public object Get(GetPublicSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - var publicInfo = new PublicSystemInfo - { - Id = result.Id, - ServerName = result.ServerName, - Version = result.Version - }; - - return ToOptimizedResult(publicInfo); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(RestartApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Restart().ConfigureAwait(false); - }); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(ShutdownApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); - }); - } - - } -} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f5a1f54cbb..4ffe5b3911 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Api var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); - if ((Request.IsLocal && isDashboard) || + if ((Request.IsLocal && isDashboard) || !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers @@ -327,7 +327,7 @@ namespace MediaBrowser.Api var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); - + var task = _userManager.DeleteUser(user); Task.WaitAll(task); @@ -374,8 +374,17 @@ namespace MediaBrowser.Api auth.DeviceId = "Unknown device id"; } - var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; + var result = _sessionMananger.AuthenticateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + Password = request.Password, + RemoteEndPoint = Request.RemoteIp, + Username = request.Username + + }, Request.IsLocal).Result; return ToOptimizedResult(result); } @@ -457,8 +466,8 @@ namespace MediaBrowser.Api Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? - _userManager.UpdateUser(user) : + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs deleted file mode 100644 index 600d9e405a..0000000000 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ /dev/null @@ -1,116 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Session; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SessionInfoWebSocketListener - /// - class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "Sessions"; } - } - - /// - /// The _kernel - /// - private readonly ISessionManager _sessionManager; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The session manager. - public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) - : base(logger) - { - _sessionManager = sessionManager; - - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity += _sessionManager_SessionActivity; - } - - void _sessionManager_SessionActivity(object sender, SessionEventArgs e) - { - SendData(false); - } - - void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - SendData(false); - } - - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) - { - SendData(true); - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); - } - - protected override bool SendOnTimer - { - get - { - return false; - } - } - - protected override void Dispose(bool dispose) - { - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity -= _sessionManager_SessionActivity; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs deleted file mode 100644 index 2940bcef06..0000000000 --- a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.System; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SystemInfoWebSocketListener - /// - public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "SystemInfo"; } - } - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The app host. - public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) - : base(logger) - { - _appHost = appHost; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_appHost.GetSystemInfo()); - } - } -} diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs index 60abc14f1a..cb6121c9ff 100644 --- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs +++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Common.Implementations.Configuration /// public event EventHandler ConfigurationUpdated; + /// + /// Occurs when [configuration updating]. + /// + public event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [named configuration updated]. /// @@ -217,6 +222,13 @@ namespace MediaBrowser.Common.Implementations.Configuration throw new ArgumentException("Expected configuration type is " + configurationType.Name); } + EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs + { + Key = key, + NewConfiguration = configuration + + }, Logger); + _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); var path = GetConfigurationFile(key); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 68222d8436..0dc67f8c00 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -547,6 +547,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks if (ex != null) { result.ErrorMessage = ex.Message; + result.LongErrorMessage = ex.StackTrace; } var path = GetHistoryFilePath(); diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index 25698d9729..d826a3ee78 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Common.Configuration { public interface IConfigurationManager { + /// + /// Occurs when [configuration updating]. + /// + event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [configuration updated]. /// diff --git a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs index fc69630709..6989dea06b 100644 --- a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs @@ -13,4 +13,9 @@ /// true if this instance is enabled; otherwise, false. bool IsEnabled { get; } } + + public interface IScheduledTaskActivityLog + { + bool IsActivityLogged { get; } + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Activity/IActivityManager.cs b/MediaBrowser.Controller/Activity/IActivityManager.cs new file mode 100644 index 0000000000..0c565ae36c --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityManager.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityManager + { + event EventHandler> EntryCreated; + + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Activity/IActivityRepository.cs b/MediaBrowser.Controller/Activity/IActivityRepository.cs new file mode 100644 index 0000000000..29e60ff1f0 --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityRepository.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityRepository + { + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index aac8cda2eb..13c9f8d84b 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,7 +1,5 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; -using System; namespace MediaBrowser.Controller.Configuration { @@ -10,11 +8,6 @@ namespace MediaBrowser.Controller.Configuration /// public interface IServerConfigurationManager : IConfigurationManager { - /// - /// Occurs when [configuration updating]. - /// - event EventHandler> ConfigurationUpdating; - /// /// Gets the application paths. /// diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 5e6bd97079..19c9601671 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -102,17 +102,9 @@ namespace MediaBrowser.Controller.Entities.Movies var totalItems = items.Count; var percentages = new Dictionary(totalItems); - var tasks = new List(); - // Refresh songs foreach (var item in items) { - if (tasks.Count >= 3) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - } - cancellationToken.ThrowIfCancellationRequested(); var innerProgress = new ActionableProgress(); @@ -132,13 +124,9 @@ namespace MediaBrowser.Controller.Entities.Movies }); // Avoid implicitly captured closure - var taskChild = item; - tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken)); + await RefreshItem(item, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); } - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 0da5f92728..c6bbf02ae7 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -31,6 +31,7 @@ namespace MediaBrowser.Controller.Library event EventHandler> UserCreated; event EventHandler> UserConfigurationUpdated; + event EventHandler> UserPasswordChanged; /// /// Updates the configuration. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index d8d836597b..34486182b7 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -269,7 +269,7 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { - logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); + //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5243e1a2ae..28e1ffb1c8 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -68,6 +68,8 @@ Properties\SharedVersion.cs + + @@ -241,6 +243,7 @@ + @@ -320,6 +323,7 @@ + @@ -360,4 +364,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs index 87b89e79c8..254e56e059 100644 --- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs +++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs @@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Notifications /// event EventHandler NotificationAdded; /// - /// Occurs when [notification updated]. - /// - event EventHandler NotificationUpdated; - /// /// Occurs when [notifications marked read]. /// event EventHandler NotificationsMarkedRead; @@ -37,14 +33,6 @@ namespace MediaBrowser.Controller.Notifications /// NotificationResult. NotificationResult GetNotifications(NotificationQuery query); - /// - /// Gets the notification. - /// - /// The id. - /// The user id. - /// Notification. - Notification GetNotification(string id, string userId); - /// /// Adds the notification. /// diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs new file mode 100644 index 0000000000..38871e8147 --- /dev/null +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Controller.Session +{ + public class AuthenticationRequest + { + public string Username { get; set; } + public string Password { get; set; } + public string App { get; set; } + public string AppVersion { get; set; } + public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string RemoteEndPoint { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index e37a139233..f715ce7703 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; using System; @@ -46,6 +47,16 @@ namespace MediaBrowser.Controller.Session /// Occurs when [capabilities changed]. /// event EventHandler CapabilitiesChanged; + + /// + /// Occurs when [authentication failed]. + /// + event EventHandler> AuthenticationFailed; + + /// + /// Occurs when [authentication succeeded]. + /// + event EventHandler> AuthenticationSucceeded; /// /// Gets the sessions. @@ -211,23 +222,10 @@ namespace MediaBrowser.Controller.Session /// /// Authenticates the new session. /// - /// The username. - /// The password. - /// Type of the client. - /// The application version. - /// The device identifier. - /// Name of the device. - /// The remote end point. + /// The request. /// if set to true [is local]. /// Task{SessionInfo}. - Task AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, - string remoteEndPoint, - bool isLocal); + Task AuthenticateNewSession(AuthenticationRequest request, bool isLocal); /// /// Reports the capabilities. diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 1d66d1505b..0c814c0d4c 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Providers; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,6 +9,16 @@ namespace MediaBrowser.Controller.Subtitles { public interface ISubtitleManager { + /// + /// Occurs when [subtitle download failure]. + /// + event EventHandler SubtitleDownloadFailure; + + /// + /// Occurs when [subtitles downloaded]. + /// + event EventHandler SubtitlesDownloaded; + /// /// Adds the parts. /// @@ -31,7 +42,7 @@ namespace MediaBrowser.Controller.Subtitles /// The request. /// The cancellation token. /// Task{IEnumerable{RemoteSubtitleInfo}}. - Task> SearchSubtitles(SubtitleSearchRequest request, + Task> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken); /// @@ -41,8 +52,8 @@ namespace MediaBrowser.Controller.Subtitles /// The subtitle identifier. /// The cancellation token. /// Task. - Task DownloadSubtitles(Video video, - string subtitleId, + Task DownloadSubtitles(Video video, + string subtitleId, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs new file mode 100644 index 0000000000..1d204f2cbd --- /dev/null +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Subtitles +{ + public class SubtitleDownloadEventArgs + { + public BaseItem Item { get; set; } + + public string Format { get; set; } + + public string Language { get; set; } + + public bool IsForced { get; set; } + + public string Provider { get; set; } + } + + public class SubtitleDownloadFailureEventArgs + { + public BaseItem Item { get; set; } + + public string Provider { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index 62aec5ecbd..25778d036b 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.LocalMetadata var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -46,7 +46,7 @@ namespace MediaBrowser.LocalMetadata } finally { - XmlProviderUtils.XmlParsingResourcePool.Release(); + //XmlProviderUtils.XmlParsingResourcePool.Release(); } return result; diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ca48b88896..2a99076d42 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -83,6 +83,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\ApiClientExtensions.cs @@ -173,9 +176,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -728,6 +728,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 1adf83d36a..72414d454c 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\GeneralCommandEventArgs.cs @@ -136,9 +139,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -685,6 +685,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs new file mode 100644 index 0000000000..8fad574618 --- /dev/null +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Model.Logging; +using System; + +namespace MediaBrowser.Model.Activity +{ + public class ActivityLogEntry + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the overview. + /// + /// The overview. + public string Overview { get; set; } + + /// + /// Gets or sets the short overview. + /// + /// The short overview. + public string ShortOverview { get; set; } + + /// + /// Gets or sets the type. + /// + /// The type. + public string Type { get; set; } + + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public string ItemId { get; set; } + + /// + /// Gets or sets the date. + /// + /// The date. + public DateTime Date { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the log severity. + /// + /// The log severity. + public LogSeverity Severity { get; set; } + } +} diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 363500954a..98b765e6bd 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -623,7 +623,7 @@ namespace MediaBrowser.Model.ApiClient Task ReportPlaybackStoppedAsync(PlaybackStopInfo info); /// - /// Instructs antoher client to browse to a library item. + /// Instructs another client to browse to a library item. /// /// The session id. /// The id of the item to browse to. diff --git a/MediaBrowser.Model/Channels/ChannelItemQuery.cs b/MediaBrowser.Model/Channels/ChannelItemQuery.cs index a76c6cd2d3..4aacc16194 100644 --- a/MediaBrowser.Model/Channels/ChannelItemQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelItemQuery.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System.Collections.Generic; namespace MediaBrowser.Model.Channels { @@ -39,13 +38,13 @@ namespace MediaBrowser.Model.Channels public SortOrder? SortOrder { get; set; } public string[] SortBy { get; set; } public ItemFilter[] Filters { get; set; } - public List Fields { get; set; } + public ItemFields[] Fields { get; set; } public ChannelItemQuery() { Filters = new ItemFilter[] { }; SortBy = new string[] { }; - Fields = new List(); + Fields = new ItemFields[] { }; } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 4734e2af7d..6600a3e912 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Model.Configuration { @@ -285,8 +286,6 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - - SubtitleOptions = new SubtitleOptions(); } } } diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Configuration/SubtitleOptions.cs deleted file mode 100644 index d50dba1b2e..0000000000 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class SubtitleOptions - { - public bool SkipIfGraphicalSubtitlesPresent { get; set; } - public bool SkipIfAudioTrackMatches { get; set; } - public string[] DownloadLanguages { get; set; } - public bool DownloadMovieSubtitles { get; set; } - public bool DownloadEpisodeSubtitles { get; set; } - - public string OpenSubtitlesUsername { get; set; } - public string OpenSubtitlesPasswordHash { get; set; } - public bool IsOpenSubtitleVipAccount { get; set; } - - public SubtitleOptions() - { - DownloadLanguages = new string[] { }; - - SkipIfAudioTrackMatches = true; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 5a83419e19..3c558577ad 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -13,5 +13,21 @@ namespace MediaBrowser.Model.Events /// /// The argument. public T Argument { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The argument. + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// + /// Initializes a new instance of the class. + /// + public GenericEventArgs() + { + } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 042828887f..75694cb04d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -59,6 +59,7 @@ Properties\SharedVersion.cs + @@ -99,7 +100,7 @@ - + @@ -378,4 +379,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs new file mode 100644 index 0000000000..84f01e0b7a --- /dev/null +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -0,0 +1,22 @@ +namespace MediaBrowser.Model.Providers +{ + public class SubtitleOptions + { + public bool SkipIfGraphicalSubtitlesPresent { get; set; } + public bool SkipIfAudioTrackMatches { get; set; } + public string[] DownloadLanguages { get; set; } + public bool DownloadMovieSubtitles { get; set; } + public bool DownloadEpisodeSubtitles { get; set; } + + public string OpenSubtitlesUsername { get; set; } + public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } + + public SubtitleOptions() + { + DownloadLanguages = new string[] { }; + + SkipIfAudioTrackMatches = true; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index e73b4c9a11..956d68ae4d 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -42,5 +42,11 @@ namespace MediaBrowser.Model.Tasks /// /// The error message. public string ErrorMessage { get; set; } + + /// + /// Gets or sets the long error message. + /// + /// The long error message. + public string LongErrorMessage { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 76a1e52f5b..66188f796a 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -152,6 +152,7 @@ + @@ -213,4 +214,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index a2e1ba05a7..f48707582c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -13,10 +13,12 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -464,6 +466,11 @@ namespace MediaBrowser.Providers.MediaInfo } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + /// /// Adds the external subtitles. /// @@ -484,9 +491,11 @@ namespace MediaBrowser.Providers.MediaInfo var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; - if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + var subtitleOptions = GetOptions(); + + if (enableSubtitleDownloading && (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (subtitleOptions.DownloadMovieSubtitles && video is Movie)) { var downloadedLanguages = await new SubtitleDownloader(_logger, @@ -494,9 +503,9 @@ namespace MediaBrowser.Providers.MediaInfo .DownloadSubtitles(video, currentStreams, externalSubtitleStreams, - _config.Configuration.SubtitleOptions.SkipIfGraphicalSubtitlesPresent, - _config.Configuration.SubtitleOptions.SkipIfAudioTrackMatches, - _config.Configuration.SubtitleOptions.DownloadLanguages, + subtitleOptions.SkipIfGraphicalSubtitlesPresent, + subtitleOptions.SkipIfAudioTrackMatches, + subtitleOptions.DownloadLanguages, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 361cc317c2..63df3f50d5 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.ScheduledTasks; 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.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -12,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -45,8 +48,15 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Library"; } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + public async Task Execute(CancellationToken cancellationToken, IProgress progress) { + var options = GetOptions(); + var videos = _libraryManager.RootFolder .RecursiveChildren .OfType