From 608ebf4829e7e394170bb2dec8a33c83600e9c08 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 28 Jun 2014 15:35:30 -0400 Subject: update video player layout --- MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs index 0f7e94ac5..3c8f86b1e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Logging; +using System.Globalization; +using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Net; @@ -45,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer //log.AppendLine("Headers: " + string.Join(",", response.Headers.AllKeys.Select(k => k + "=" + response.Headers[k]))); - var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds); + var responseTime = string.Format(". Response time: {0} ms. Content length: {1} bytes.", duration.TotalMilliseconds, response.ContentLength64.ToString(CultureInfo.InvariantCulture)); var msg = "HTTP Response " + statusCode + " to " + endPoint + responseTime; -- cgit v1.2.3 From 389390b82ecfbb48e0486f8f132046ddf8624e00 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 2 Jul 2014 00:57:18 -0400 Subject: fixes #789 - Security Issue: API allows access to any folder of the PC running MediaBrowser --- MediaBrowser.Api/AppThemeService.cs | 2 + .../AuthorizationRequestFilterAttribute.cs | 190 --------------------- MediaBrowser.Api/BaseApiService.cs | 13 +- MediaBrowser.Api/ChannelService.cs | 4 +- MediaBrowser.Api/DisplayPreferencesService.cs | 4 +- MediaBrowser.Api/EnvironmentService.cs | 2 + MediaBrowser.Api/GamesService.cs | 2 + MediaBrowser.Api/Library/LibraryService.cs | 2 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - MediaBrowser.Api/Playback/BaseStreamingService.cs | 7 - MediaBrowser.Api/SessionsService.cs | 16 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 10 +- MediaBrowser.Api/UserService.cs | 7 +- MediaBrowser.Controller/Entities/Folder.cs | 21 +-- .../MediaBrowser.Controller.csproj | 8 + .../Net/AuthenticatedAttribute.cs | 41 +++++ MediaBrowser.Controller/Net/AuthorizationInfo.cs | 32 ++++ MediaBrowser.Controller/Net/IAuthService.cs | 9 + .../Net/IAuthorizationContext.cs | 14 ++ MediaBrowser.Controller/Net/IHasAuthorization.cs | 12 ++ MediaBrowser.Controller/Net/IHasResultFactory.cs | 3 +- MediaBrowser.Controller/Net/IHasSession.cs | 12 ++ MediaBrowser.Controller/Net/IRestfulService.cs | 1 + MediaBrowser.Controller/Net/ISessionContext.cs | 13 ++ MediaBrowser.Controller/Net/LoggedAttribute.cs | 73 ++++++++ MediaBrowser.Providers/Manager/MetadataService.cs | 10 +- .../Collections/ManualCollectionsFolder.cs | 8 +- .../HttpServer/HttpListenerHost.cs | 34 ++-- .../HttpServer/RangeRequestWriter.cs | 2 + .../HttpServer/Security/AuthService.cs | 113 ++++++++++++ .../HttpServer/Security/AuthorizationContext.cs | 96 +++++++++++ .../HttpServer/Security/SessionAuthProvider.cs | 35 ++++ .../HttpServer/Security/SessionContext.cs | 36 ++++ .../MediaBrowser.Server.Implementations.csproj | 4 + MediaBrowser.ServerApplication/ApplicationHost.cs | 8 +- .../Native/BrowserLauncher.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 3 + .../Providers/SeriesNfoProvider.cs | 2 +- 39 files changed, 587 insertions(+), 267 deletions(-) delete mode 100644 MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs create mode 100644 MediaBrowser.Controller/Net/AuthenticatedAttribute.cs create mode 100644 MediaBrowser.Controller/Net/AuthorizationInfo.cs create mode 100644 MediaBrowser.Controller/Net/IAuthService.cs create mode 100644 MediaBrowser.Controller/Net/IAuthorizationContext.cs create mode 100644 MediaBrowser.Controller/Net/IHasAuthorization.cs create mode 100644 MediaBrowser.Controller/Net/IHasSession.cs create mode 100644 MediaBrowser.Controller/Net/ISessionContext.cs create mode 100644 MediaBrowser.Controller/Net/LoggedAttribute.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/AppThemeService.cs b/MediaBrowser.Api/AppThemeService.cs index 4d8eed7dd..0c8a0aaa6 100644 --- a/MediaBrowser.Api/AppThemeService.cs +++ b/MediaBrowser.Api/AppThemeService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Themes; using MediaBrowser.Model.Themes; using ServiceStack; @@ -47,6 +48,7 @@ namespace MediaBrowser.Api { } + [Authenticated] public class AppThemeService : BaseApiService { private readonly IAppThemeManager _themeManager; diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs deleted file mode 100644 index 6c56083cb..000000000 --- a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs +++ /dev/null @@ -1,190 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using ServiceStack.Web; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Api -{ - public class AuthorizationRequestFilterAttribute : Attribute, IHasRequestFilter - { - //This property will be resolved by the IoC container - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } - - public ISessionManager SessionManager { get; set; } - - /// - /// Gets or sets the logger. - /// - /// The logger. - public ILogger Logger { get; set; } - - /// - /// The request filter is executed before the service. - /// - /// The http request wrapper - /// The http response wrapper - /// The request DTO - public void RequestFilter(IRequest request, IResponse response, object requestDto) - { - //This code is executed before the service - var auth = GetAuthorizationDictionary(request); - - if (auth != null) - { - User user = null; - - if (auth.ContainsKey("UserId")) - { - var userId = auth["UserId"]; - - if (!string.IsNullOrEmpty(userId)) - { - user = UserManager.GetUserById(new Guid(userId)); - } - } - - string deviceId; - string device; - string client; - string version; - - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - - if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) - { - var remoteEndPoint = request.RemoteIp; - - SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); - } - } - } - - /// - /// Gets the auth. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorizationDictionary(IRequest httpReq) - { - var auth = httpReq.Headers["Authorization"]; - - return GetAuthorization(auth); - } - - public static User GetCurrentUser(IRequest httpReq, IUserManager userManager) - { - var info = GetAuthorization(httpReq); - - return string.IsNullOrEmpty(info.UserId) ? null : - userManager.GetUserById(new Guid(info.UserId)); - } - - /// - /// Gets the authorization. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - public static AuthorizationInfo GetAuthorization(IRequest httpReq) - { - var auth = GetAuthorizationDictionary(httpReq); - - string userId = null; - string deviceId = null; - string device = null; - string client = null; - string version = null; - - if (auth != null) - { - auth.TryGetValue("UserId", out userId); - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - } - - return new AuthorizationInfo - { - Client = client, - Device = device, - DeviceId = deviceId, - UserId = userId, - Version = version - }; - } - - /// - /// Gets the authorization. - /// - /// The authorization header. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorization(string authorizationHeader) - { - if (authorizationHeader == null) return null; - - var parts = authorizationHeader.Split(' '); - - // There should be at least to parts - if (parts.Length < 2) return null; - - // It has to be a digest request - if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Remove uptil the first space - authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); - parts = authorizationHeader.Split(','); - - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split(new[] { '=' }, 2); - result.Add(param[0], param[1].Trim(new[] { '"' })); - } - - return result; - } - - /// - /// A new shallow copy of this filter is used on every request. - /// - /// IHasRequestFilter. - public IHasRequestFilter Copy() - { - return this; - } - - /// - /// Order in which Request Filters are executed. - /// <0 Executed before global request filters - /// >0 Executed after global request filters - /// - /// The priority. - public int Priority - { - get { return 0; } - } - } - - public class AuthorizationInfo - { - public string UserId; - public string DeviceId; - public string Device; - public string Client; - public string Version; - } -} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index f1d596213..09eb1ea41 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -14,8 +14,7 @@ namespace MediaBrowser.Api /// /// Class BaseApiService /// - [AuthorizationRequestFilter] - public class BaseApiService : IHasResultFactory, IRestfulService + public class BaseApiService : IHasResultFactory, IRestfulService, IHasSession { /// /// Gets or sets the logger. @@ -35,6 +34,8 @@ namespace MediaBrowser.Api /// The request context. public IRequest Request { get; set; } + public ISessionContext SessionContext { get; set; } + public string GetHeader(string name) { return Request.Headers[name]; @@ -82,13 +83,11 @@ namespace MediaBrowser.Api /// /// Gets the session. /// - /// The session manager. /// SessionInfo. - protected SessionInfo GetSession(ISessionManager sessionManager) + /// Session not found. + protected SessionInfo GetSession() { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - var session = sessionManager.GetSession(auth.DeviceId, auth.Client, auth.Version); + var session = SessionContext.GetSession(Request); if (session == null) { diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index d71db929f..2cc046f1d 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -172,7 +173,8 @@ namespace MediaBrowser.Api [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } } - + + [Authenticated] public class ChannelService : BaseApiService { private readonly IChannelManager _channelManager; diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 206f5bf7b..9f3a6134e 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using ServiceStack; @@ -48,6 +49,7 @@ namespace MediaBrowser.Api /// /// Class DisplayPreferencesService /// + [Authenticated] public class DisplayPreferencesService : BaseApiService { /// diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 56f71fc00..590deff5a 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using ServiceStack; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api /// /// Class EnvironmentService /// + [Authenticated] public class EnvironmentService : BaseApiService { const char UncSeparator = '\\'; diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index ff2771ce1..9aba2b065 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using ServiceStack; @@ -51,6 +52,7 @@ namespace MediaBrowser.Api /// /// Class GamesService /// + [Authenticated] public class GamesService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index ddb2dc943..77a714755 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -470,7 +470,7 @@ namespace MediaBrowser.Api.Library { var item = _libraryManager.GetItemById(request.Id); - var session = GetSession(_sessionManager); + var session = GetSession(); if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion) { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 6973a233a..de01628f8 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -280,7 +280,7 @@ namespace MediaBrowser.Api.LiveTv private void AssertUserCanManageLiveTv() { - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, _userManager); + var user = SessionContext.GetUser(Request); if (user == null) { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 3f1d9fe67..a68966b33 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -79,7 +79,6 @@ - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 380ece2f2..9ff482a1a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1386,8 +1386,6 @@ namespace MediaBrowser.Api.Playback ParseParams(request); } - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager); - var url = Request.PathInfo; if (string.IsNullOrEmpty(request.AudioCodec)) @@ -1409,11 +1407,6 @@ namespace MediaBrowser.Api.Playback var item = LibraryManager.GetItemById(request.Id); - if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) - { - throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name)); - } - List mediaStreams = null; state.ItemType = item.GetType().Name; diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 36f1d6577..00c307a18 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.Api SeekPositionTicks = request.SeekPositionTicks }; - var task = _sessionManager.SendPlaystateCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -303,7 +303,7 @@ namespace MediaBrowser.Api ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -318,7 +318,7 @@ namespace MediaBrowser.Api if (Enum.TryParse(request.Command, true, out commandType)) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -345,7 +345,7 @@ namespace MediaBrowser.Api Text = request.Text }; - var task = _sessionManager.SendMessageCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -364,14 +364,14 @@ namespace MediaBrowser.Api StartPositionTicks = request.StartPositionTicks }; - var task = _sessionManager.SendPlayCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } public void Post(SendGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -386,7 +386,7 @@ namespace MediaBrowser.Api public void Post(SendFullGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; @@ -409,7 +409,7 @@ namespace MediaBrowser.Api { if (string.IsNullOrWhiteSpace(request.Id)) { - request.Id = GetSession(_sessionManager).Id; + request.Id = GetSession().Id; } _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities { diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index a1625d052..da12a9e3d 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.UserLibrary datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); @@ -826,7 +826,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStart request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStart(request); @@ -854,7 +854,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackProgress request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackProgress(request); @@ -877,7 +877,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStopped request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStopped(request); @@ -899,7 +899,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 1932c8f93..764a28102 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; @@ -152,7 +153,7 @@ namespace MediaBrowser.Api /// /// Class UsersService /// - public class UserService : BaseApiService + public class UserService : BaseApiService, IHasAuthorization { /// /// The _XML serializer @@ -166,6 +167,8 @@ namespace MediaBrowser.Api private readonly IDtoService _dtoService; private readonly ISessionManager _sessionMananger; + public IAuthorizationContext AuthorizationContext { get; set; } + /// /// Initializes a new instance of the class. /// @@ -295,7 +298,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + var auth = AuthorizationContext.GetAuthorizationInfo(Request); // Login in the old way if the header is missing if (string.IsNullOrEmpty(auth.Client) || diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 52c414ae8..584091b13 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -797,9 +797,8 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// The list. /// if set to true [recursive]. - /// The filter. /// true if XXXX, false otherwise - private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive, Func filter) + private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive) { var hasLinkedChildren = false; @@ -807,19 +806,16 @@ namespace MediaBrowser.Controller.Entities { if (child.IsVisible(user)) { - if (filter == null || filter(child)) + if (!child.IsHiddenFromUser(user)) { - if (!child.IsHiddenFromUser(user)) - { - list.Add(child); - } + list.Add(child); } if (recursive && child.IsFolder) { var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true)) { hasLinkedChildren = true; } @@ -831,11 +827,6 @@ namespace MediaBrowser.Controller.Entities { foreach (var child in GetLinkedChildren()) { - if (filter != null && !filter(child)) - { - continue; - } - if (child.IsVisible(user)) { hasLinkedChildren = true; @@ -864,7 +855,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1310e7797..ae7118be9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -195,10 +195,18 @@ + + + + + + + + diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs new file mode 100644 index 000000000..567d20f39 --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -0,0 +1,41 @@ +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class AuthenticatedAttribute : Attribute, IHasRequestFilter + { + public IAuthService AuthService { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + AuthService.Authenticate(request, response, requestDto); + } + + /// + /// A new shallow copy of this filter is used on every request. + /// + /// IHasRequestFilter. + public IHasRequestFilter Copy() + { + return this; + } + + /// + /// Order in which Request Filters are executed. + /// <0 Executed before global request filters + /// >0 Executed after global request filters + /// + /// The priority. + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs new file mode 100644 index 000000000..e609204d6 --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -0,0 +1,32 @@ + +namespace MediaBrowser.Controller.Net +{ + public class AuthorizationInfo + { + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + /// + /// Gets or sets the device. + /// + /// The device. + public string Device { get; set; } + /// + /// Gets or sets the client. + /// + /// The client. + public string Client { get; set; } + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs new file mode 100644 index 000000000..41859395b --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -0,0 +1,9 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthService + { + void Authenticate(IRequest request, IResponse response, object requestDto); + } +} diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs new file mode 100644 index 000000000..9cf562370 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -0,0 +1,14 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthorizationContext + { + /// + /// Gets the authorization information. + /// + /// The request context. + /// AuthorizationInfo. + AuthorizationInfo GetAuthorizationInfo(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/IHasAuthorization.cs b/MediaBrowser.Controller/Net/IHasAuthorization.cs new file mode 100644 index 000000000..6fc70159d --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasAuthorization.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasAuthorization + { + /// + /// Gets or sets the authorization context. + /// + /// The authorization context. + IAuthorizationContext AuthorizationContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IHasResultFactory.cs b/MediaBrowser.Controller/Net/IHasResultFactory.cs index a87113d1f..3988b8d61 100644 --- a/MediaBrowser.Controller/Net/IHasResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHasResultFactory.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using ServiceStack.Web; +using ServiceStack.Web; namespace MediaBrowser.Controller.Net { diff --git a/MediaBrowser.Controller/Net/IHasSession.cs b/MediaBrowser.Controller/Net/IHasSession.cs new file mode 100644 index 000000000..e762c1e84 --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasSession.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasSession + { + /// + /// Gets or sets the session context. + /// + /// The session context. + ISessionContext SessionContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IRestfulService.cs b/MediaBrowser.Controller/Net/IRestfulService.cs index f55012b73..7d07bb094 100644 --- a/MediaBrowser.Controller/Net/IRestfulService.cs +++ b/MediaBrowser.Controller/Net/IRestfulService.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Net /// /// Interface IRestfulService /// + [Logged] public interface IRestfulService : IService { } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs new file mode 100644 index 000000000..31ccd5373 --- /dev/null +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface ISessionContext + { + SessionInfo GetSession(IRequest requestContext); + + User GetUser(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/LoggedAttribute.cs b/MediaBrowser.Controller/Net/LoggedAttribute.cs new file mode 100644 index 000000000..6df72f7a7 --- /dev/null +++ b/MediaBrowser.Controller/Net/LoggedAttribute.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class LoggedAttribute : Attribute, IHasRequestFilter + { + public ILogger Logger { get; set; } + public IUserManager UserManager { get; set; } + public ISessionManager SessionManager { get; set; } + public IAuthorizationContext AuthorizationContext { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + //This code is executed before the service + var auth = AuthorizationContext.GetAuthorizationInfo(request); + + if (auth != null) + { + User user = null; + + if (!string.IsNullOrWhiteSpace(auth.UserId)) + { + var userId = auth.UserId; + + user = UserManager.GetUserById(new Guid(userId)); + } + + string deviceId = auth.DeviceId; + string device = auth.Device; + string client = auth.Client; + string version = auth.Version; + + if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + { + var remoteEndPoint = request.RemoteIp; + + SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + } + } + } + + /// + /// A new shallow copy of this filter is used on every request. + /// + /// IHasRequestFilter. + public IHasRequestFilter Copy() + { + return this; + } + + /// + /// Order in which Request Filters are executed. + /// <0 Executed before global request filters + /// >0 Executed after global request filters + /// + /// The priority. + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 717b93524..95eca6ba0 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -340,13 +340,17 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { + Logger.ErrorException("Error in {0}", ex, provider.Name); + // If a local provider fails, consider that a failure refreshResult.Status = ProviderRefreshStatus.Failure; refreshResult.ErrorMessage = ex.Message; - Logger.ErrorException("Error in {0}", ex, provider.Name); - // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost - return refreshResult; + if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh) + { + // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost + return refreshResult; + } } } diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index 739b42192..07c86ed31 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -12,12 +12,8 @@ namespace MediaBrowser.Server.Implementations.Collections public override bool IsVisible(User user) { - if (!GetChildren(user, true).Any()) - { - return false; - } - - return base.IsVisible(user); + return GetChildren(user, true).Any() && + base.IsVisible(user); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 0fc9265f6..833dfc5e4 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,13 +1,13 @@ -using System.Net.Sockets; -using System.Runtime.Serialization; -using Funq; +using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.HttpServer.Security; using ServiceStack; using ServiceStack.Api.Swagger; +using ServiceStack.Auth; using ServiceStack.Host; using ServiceStack.Host.Handlers; using ServiceStack.Host.HttpListener; @@ -27,7 +27,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { public class HttpListenerHost : ServiceStackHost, IHttpServer { - private string ServerName { get; set; } private string HandlerPath { get; set; } private string DefaultRedirectPath { get; set; } @@ -59,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer : base(serviceName, assembliesWithServices) { DefaultRedirectPath = defaultRedirectPath; - ServerName = serviceName; HandlerPath = handlerPath; _logger = logManager.GetLogger("HttpServer"); @@ -95,7 +93,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer container.Adapter = _containerAdapter; Plugins.Add(new SwaggerFeature()); - Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + + Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { + new SessionAuthProvider(_containerAdapter.Resolve()), + })); + HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } @@ -112,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Config.HandlerFactoryPath = string.IsNullOrEmpty(HandlerPath) ? null - : HandlerPath; + : "/" + HandlerPath; Config.MetadataRedirectPath = string.IsNullOrEmpty(HandlerPath) ? "metadata" @@ -161,8 +164,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (Listener == null) Listener = new HttpListener(); - HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - foreach (var prefix in UrlPrefixes) { _logger.Info("Adding HttpListener prefix " + prefix); @@ -172,6 +173,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer IsStarted = true; _logger.Info("Starting HttpListner"); Listener.Start(); + _logger.Info("HttpListener started"); for (var i = 0; i < _autoResetEvents.Count; i++) { @@ -263,27 +265,27 @@ namespace MediaBrowser.Server.Implementations.HttpServer var localPath = request.Url.LocalPath; - if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) { context.Response.Redirect(DefaultRedirectPath); context.Response.Close(); return; } - if (string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.IsNullOrEmpty(localPath)) { - context.Response.Redirect("/mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } @@ -410,6 +412,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); req.RequestAttributes = req.GetAttributes(); + return req; } @@ -442,7 +445,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; + //var pathInfo = httpReq.PathInfo; + var handler = HttpHandlerFactory.GetHandler(httpReq); + //var handler = HttpHandlerFactory.GetHandlerForPathInfo(httpReq.HttpMethod, pathInfo, pathInfo, httpReq.GetPhysicalPath()); var serviceStackHandler = handler as IServiceStackHandler; if (serviceStackHandler != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 1ff199eb4..9de6972f8 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -228,5 +228,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } public string StatusDescription { get; set; } + + public int PaddingLength { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs new file mode 100644 index 000000000..ddb583f5d --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -0,0 +1,113 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; +using ServiceStack.Web; +using System; +using System.Collections.Specialized; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthService : IAuthService + { + /// + /// Restrict authentication to a specific . + /// For example, if this attribute should only permit access + /// if the user is authenticated with , + /// you should set this property to . + /// + public string Provider { get; set; } + + /// + /// Redirect the client to a specific URL if authentication failed. + /// If this property is null, simply `401 Unauthorized` is returned. + /// + public string HtmlRedirect { get; set; } + + public void Authenticate(IRequest req, IResponse res, object requestDto) + { + if (HostContext.HasValidAuthSecret(req)) + return; + + ExecuteBasic(req, res, requestDto); //first check if session is authenticated + if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) + + ValidateUser(req); + } + + private void ValidateUser(IRequest req) + { + var user = req.TryResolve().GetUser(req); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("Unauthorized access."); + } + } + + private void ExecuteBasic(IRequest req, IResponse res, object requestDto) + { + if (AuthenticateService.AuthProviders == null) + throw new InvalidOperationException( + "The AuthService must be initialized by calling AuthService.Init to use an authenticate attribute"); + + var matchingOAuthConfigs = AuthenticateService.AuthProviders.Where(x => + this.Provider.IsNullOrEmpty() + || x.Provider == this.Provider).ToList(); + + if (matchingOAuthConfigs.Count == 0) + { + res.WriteError(req, requestDto, "No OAuth Configs found matching {0} provider" + .Fmt(this.Provider ?? "any")); + res.EndRequest(); + } + + matchingOAuthConfigs.OfType() + .Each(x => x.PreAuthenticate(req, res)); + + var session = req.GetSession(); + if (session == null || !matchingOAuthConfigs.Any(x => session.IsAuthorized(x.Provider))) + { + if (this.DoHtmlRedirectIfConfigured(req, res, true)) return; + + AuthProvider.HandleFailedAuth(matchingOAuthConfigs[0], session, req, res); + } + } + + protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) + { + var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; + if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html)) + { + DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam); + return true; + } + return false; + } + + public static void DoHtmlRedirect(string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam) + { + var url = req.ResolveAbsoluteUrl(redirectUrl); + if (includeRedirectParam) + { + var absoluteRequestPath = req.ResolveAbsoluteUrl("~" + req.PathInfo + ToQueryString(req.QueryString)); + url = url.AddQueryParam(HostContext.ResolveLocalizedString(LocalizedStrings.Redirect), absoluteRequestPath); + } + + res.RedirectToUrl(url); + } + + private static string ToQueryString(INameValueCollection queryStringCollection) + { + return ToQueryString((NameValueCollection)queryStringCollection.Original); + } + + private static string ToQueryString(NameValueCollection queryStringCollection) + { + if (queryStringCollection == null || queryStringCollection.Count == 0) + return String.Empty; + + return "?" + queryStringCollection.ToFormUrlEncoded(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs new file mode 100644 index 000000000..6ea77f251 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Net; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthorizationContext : IAuthorizationContext + { + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) + { + return GetAuthorization(requestContext); + } + + /// + /// Gets the authorization. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static AuthorizationInfo GetAuthorization(IRequest httpReq) + { + var auth = GetAuthorizationDictionary(httpReq); + + string userId = null; + string deviceId = null; + string device = null; + string client = null; + string version = null; + + if (auth != null) + { + auth.TryGetValue("UserId", out userId); + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out device); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + } + + return new AuthorizationInfo + { + Client = client, + Device = device, + DeviceId = deviceId, + UserId = userId, + Version = version + }; + } + + /// + /// Gets the auth. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorizationDictionary(IRequest httpReq) + { + var auth = httpReq.Headers["Authorization"]; + + return GetAuthorization(auth); + } + + /// + /// Gets the authorization. + /// + /// The authorization header. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorization(string authorizationHeader) + { + if (authorizationHeader == null) return null; + + var parts = authorizationHeader.Split(' '); + + // There should be at least to parts + if (parts.Length < 2) return null; + + // It has to be a digest request + if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Remove uptil the first space + authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); + parts = authorizationHeader.Split(','); + + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var item in parts) + { + var param = item.Trim().Split(new[] { '=' }, 2); + result.Add(param[0], param[1].Trim(new[] { '"' })); + } + + return result; + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs new file mode 100644 index 000000000..7c3173101 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionAuthProvider : CredentialsAuthProvider + { + private readonly ISessionContext _sessionContext; + + public SessionAuthProvider(ISessionContext sessionContext) + { + _sessionContext = sessionContext; + } + + public override bool TryAuthenticate(IServiceBase authService, string userName, string password) + { + return true; + } + + public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null) + { + return true; + } + + protected override void SaveUserAuth(IServiceBase authService, IAuthSession session, IAuthRepository authRepo, IAuthTokens tokens) + { + } + + public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request) + { + return base.Authenticate(authService, session, request); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs new file mode 100644 index 000000000..f67c643c8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionContext : ISessionContext + { + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + private readonly IAuthorizationContext _authContext; + + public SessionContext(IUserManager userManager, IAuthorizationContext authContext, ISessionManager sessionManager) + { + _userManager = userManager; + _authContext = authContext; + _sessionManager = sessionManager; + } + + public SessionInfo GetSession(IRequest requestContext) + { + var authorization = _authContext.GetAuthorizationInfo(requestContext); + + return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version); + } + + public User GetUser(IRequest requestContext) + { + var session = GetSession(requestContext); + + return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 340107851..10e27bc30 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -133,6 +133,7 @@ + @@ -141,9 +142,12 @@ + + + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 87f18e4cb..dbca94065 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -62,6 +62,7 @@ using MediaBrowser.Server.Implementations.Dto; using MediaBrowser.Server.Implementations.EntryPoints; using MediaBrowser.Server.Implementations.FileOrganization; using MediaBrowser.Server.Implementations.HttpServer; +using MediaBrowser.Server.Implementations.HttpServer.Security; using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; using MediaBrowser.Server.Implementations.LiveTv; @@ -598,7 +599,7 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); - HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html"); + HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", WebApplicationName, "dashboard/index.html"); RegisterSingleInstance(HttpServer, false); progress.Report(10); @@ -667,6 +668,11 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + var authContext = new AuthorizationContext(); + RegisterSingleInstance(authContext); + RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); + RegisterSingleInstance(new AuthService()); + RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs index 77a543ad7..4ba98e9b6 100644 --- a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.ServerApplication.Native public static void OpenDashboardPage(string page, User loggedInUser, IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger) { var url = "http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + - appHost.WebApplicationName + "/dashboard/" + page; + appHost.WebApplicationName + "/web/" + page; OpenUrl(url, logger); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 2ca0669cd..80f864a32 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPages /// [Route("/dashboard/ConfigurationPages", "GET")] + [Route("/web/ConfigurationPages", "GET")] public class GetDashboardConfigurationPages : IReturn> { /// @@ -38,6 +39,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPage /// [Route("/dashboard/ConfigurationPage", "GET")] + [Route("/web/ConfigurationPage", "GET")] public class GetDashboardConfigurationPage { /// @@ -50,6 +52,7 @@ namespace MediaBrowser.WebDashboard.Api /// /// Class GetDashboardResource /// + [Route("/web/{ResourceName*}", "GET")] [Route("/dashboard/{ResourceName*}", "GET")] public class GetDashboardResource { diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 4a71d9fd3..9db0cc8fc 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.XbmcMetadata.Providers protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return directoryService.GetFile(Path.Combine(info.Path, "series.nfo")); + return directoryService.GetFile(Path.Combine(info.Path, "tvshow.nfo")); } } } -- cgit v1.2.3 From 9bab99d4d8659e69478722d72028e84b64575ab2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 2 Jul 2014 01:16:59 -0400 Subject: run all ajax through apiclient --- .../HttpServer/Security/AuthService.cs | 48 +++++++++++++++++++++- MediaBrowser.ServerApplication/ApplicationHost.cs | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index ddb583f5d..1a7f9db28 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,4 +1,7 @@ -using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; using ServiceStack; using ServiceStack.Auth; using ServiceStack.Web; @@ -10,6 +13,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { + public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext) + { + AuthorizationContext = authorizationContext; + SessionManager = sessionManager; + UserManager = userManager; + } + + public IUserManager UserManager { get; private set; } + public ISessionManager SessionManager { get; private set; } + public IAuthorizationContext AuthorizationContext { get; private set; } + /// /// Restrict authentication to a specific . /// For example, if this attribute should only permit access @@ -37,7 +51,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security private void ValidateUser(IRequest req) { - var user = req.TryResolve().GetUser(req); + User user = null; + + //This code is executed before the service + var auth = AuthorizationContext.GetAuthorizationInfo(req); + + if (auth != null) + { + if (!string.IsNullOrWhiteSpace(auth.UserId)) + { + var userId = auth.UserId; + + user = UserManager.GetUserById(new Guid(userId)); + } + + string deviceId = auth.DeviceId; + string device = auth.Device; + string client = auth.Client; + string version = auth.Version; + + if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + { + var remoteEndPoint = req.RemoteIp; + + SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + } + } if (user == null || user.Configuration.IsDisabled) { @@ -74,6 +113,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security } } + private void LogRequest() + { + + } + protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) { var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index dbca94065..e8fb7bfcb 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -671,7 +671,7 @@ namespace MediaBrowser.ServerApplication var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - RegisterSingleInstance(new AuthService()); + RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext)); RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); -- cgit v1.2.3 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.Api/ApiEntryPoint.cs | 60 ++- .../DefaultTheme/DefaultThemeService.cs | 2 + MediaBrowser.Api/Dlna/DlnaService.cs | 2 + MediaBrowser.Api/ItemLookupService.cs | 2 + MediaBrowser.Api/ItemRefreshService.cs | 2 + MediaBrowser.Api/ItemUpdateService.cs | 6 +- MediaBrowser.Api/Library/ChapterService.cs | 2 + .../Library/FileOrganizationService.cs | 2 + .../Library/LibraryStructureService.cs | 28 +- MediaBrowser.Api/Library/SubtitleService.cs | 2 + MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 + MediaBrowser.Api/LocalizationService.cs | 2 + MediaBrowser.Api/Movies/CollectionService.cs | 2 + MediaBrowser.Api/Movies/MoviesService.cs | 2 + MediaBrowser.Api/Movies/TrailersService.cs | 2 + MediaBrowser.Api/Music/AlbumsService.cs | 3 +- MediaBrowser.Api/Music/InstantMixService.cs | 2 + MediaBrowser.Api/PackageReviewService.cs | 2 + MediaBrowser.Api/PackageService.cs | 2 + MediaBrowser.Api/Playback/BaseStreamingService.cs | 7 - MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 12 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 10 +- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 4 +- MediaBrowser.Api/PluginService.cs | 2 + .../ScheduledTasks/ScheduledTaskService.cs | 2 + MediaBrowser.Api/SearchService.cs | 2 + MediaBrowser.Api/TvShowsService.cs | 2 + MediaBrowser.Api/UserLibrary/ArtistsService.cs | 2 + MediaBrowser.Api/UserLibrary/GameGenresService.cs | 2 + MediaBrowser.Api/UserLibrary/GenresService.cs | 2 + MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 + MediaBrowser.Api/UserLibrary/MusicGenresService.cs | 2 + MediaBrowser.Api/UserLibrary/PersonsService.cs | 2 + MediaBrowser.Api/UserLibrary/StudiosService.cs | 2 + MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 2 + MediaBrowser.Api/UserLibrary/YearsService.cs | 2 + MediaBrowser.Api/UserService.cs | 63 +--- MediaBrowser.Api/VideosService.cs | 2 + MediaBrowser.Controller/Library/IUserManager.cs | 4 +- MediaBrowser.Controller/Net/AuthorizationInfo.cs | 5 + MediaBrowser.Controller/Session/ISessionManager.cs | 13 +- .../MediaBrowser.Model.Portable.csproj | 24 +- .../MediaBrowser.Model.net35.csproj | 24 +- .../Configuration/NotificationOption.cs | 54 --- .../Configuration/NotificationOptions.cs | 124 ------- .../Configuration/NotificationType.cs | 23 -- MediaBrowser.Model/Configuration/SendToUserType.cs | 9 - .../Configuration/ServerConfiguration.cs | 8 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 8 +- .../Notifications/NotificationOption.cs | 56 +++ .../Notifications/NotificationOptions.cs | 125 +++++++ .../Notifications/NotificationType.cs | 23 ++ MediaBrowser.Model/Notifications/SendToUserType.cs | 9 + MediaBrowser.Model/Updates/PackageVersionInfo.cs | 32 +- MediaBrowser.Model/Users/AuthenticationResult.cs | 6 + .../HttpServer/Security/AuthService.cs | 25 +- .../HttpServer/Security/AuthorizationContext.cs | 3 +- .../Library/UserManager.cs | 11 +- .../LiveTv/LiveTvManager.cs | 125 ++++--- .../Localization/JavaScript/javascript.json | 18 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../NotificationConfigurationFactory.cs | 21 ++ .../Notifications/NotificationManager.cs | 16 +- .../Session/SessionManager.cs | 31 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 7 + MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- .../MediaBrowser.WebDashboard.csproj | 409 +++++++++++---------- 67 files changed, 828 insertions(+), 641 deletions(-) delete mode 100644 MediaBrowser.Model/Configuration/NotificationOption.cs delete mode 100644 MediaBrowser.Model/Configuration/NotificationOptions.cs delete mode 100644 MediaBrowser.Model/Configuration/NotificationType.cs delete mode 100644 MediaBrowser.Model/Configuration/SendToUserType.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationOption.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationOptions.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationType.cs create mode 100644 MediaBrowser.Model/Notifications/SendToUserType.cs create mode 100644 MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 2177f90c2..8e6ca4401 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; + public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); + /// /// Initializes a new instance of the class. /// @@ -301,8 +303,9 @@ namespace MediaBrowser.Api /// /// The device id. /// The delete mode. + /// if set to true [acquire lock]. /// sourcePath - internal void KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode) + internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -318,9 +321,29 @@ namespace MediaBrowser.Api jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase))); } - foreach (var job in jobs) + if (jobs.Count == 0) + { + return; + } + + if (acquireLock) { - KillTranscodingJob(job, deleteMode); + await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + } + + try + { + foreach (var job in jobs) + { + KillTranscodingJob(job, deleteMode); + } + } + finally + { + if (acquireLock) + { + TranscodingStartLock.Release(); + } } } @@ -328,10 +351,11 @@ namespace MediaBrowser.Api /// Kills the transcoding jobs. /// /// The device identifier. - /// The output path. + /// The type. /// The delete mode. + /// if set to true [acquire lock]. /// deviceId - internal void KillTranscodingJobs(string deviceId, string outputPath, FileDeleteMode deleteMode) + internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -344,12 +368,32 @@ namespace MediaBrowser.Api { // This is really only needed for HLS. // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase) && string.Equals(outputPath, i.Path, StringComparison.OrdinalIgnoreCase))); + jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase) && i.Type == type)); } - foreach (var job in jobs) + if (jobs.Count == 0) { - KillTranscodingJob(job, deleteMode); + return; + } + + if (acquireLock) + { + await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + } + + try + { + foreach (var job in jobs) + { + KillTranscodingJob(job, deleteMode); + } + } + finally + { + if (acquireLock) + { + TranscodingStartLock.Release(); + } } } diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index 8bc867a2e..21ba47bd4 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.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.Model.Entities; using MediaBrowser.Model.Logging; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api.DefaultTheme public Guid UserId { get; set; } } + [Authenticated] public class DefaultThemeService : BaseApiService { private readonly IUserManager _userManager; diff --git a/MediaBrowser.Api/Dlna/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs index 9e6ca3aea..fec6d698a 100644 --- a/MediaBrowser.Api/Dlna/DlnaService.cs +++ b/MediaBrowser.Api/Dlna/DlnaService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using ServiceStack; using System.Collections.Generic; @@ -42,6 +43,7 @@ namespace MediaBrowser.Api.Dlna { } + [Authenticated] public class DlnaService : BaseApiService { private readonly IDlnaManager _dlnaManager; diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index e68aa73f6..35b1b5385 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -6,6 +6,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.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -104,6 +105,7 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Authenticated] public class ItemLookupService : BaseApiService { private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 0094282c8..b95e18a0d 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using ServiceStack; using System; @@ -30,6 +31,7 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Authenticated] public class ItemRefreshService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index ad7da8e3c..db6c6ce53 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using ServiceStack; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,7 @@ namespace MediaBrowser.Api public string ItemId { get; set; } } + [Authenticated] public class ItemUpdateService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs index 72ffa3fca..6b8dd18f1 100644 --- a/MediaBrowser.Api/Library/ChapterService.cs +++ b/MediaBrowser.Api/Library/ChapterService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Net; using ServiceStack; using System.Linq; @@ -9,6 +10,7 @@ namespace MediaBrowser.Api.Library { } + [Authenticated] public class ChapterService : BaseApiService { private readonly IChapterManager _chapterManager; diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs index 01531a7ae..adf6a522b 100644 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ b/MediaBrowser.Api/Library/FileOrganizationService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.FileOrganization; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Querying; using ServiceStack; @@ -78,6 +79,7 @@ namespace MediaBrowser.Api.Library public bool RememberCorrection { get; set; } } + [Authenticated] public class FileOrganizationService : BaseApiService { private readonly IFileOrganizationService _iFileOrganizationService; diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 947da29fe..c3ef58768 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using ServiceStack; @@ -130,36 +131,11 @@ namespace MediaBrowser.Api.Library /// true if [refresh library]; otherwise, false. public bool RefreshLibrary { get; set; } } - - [Route("/Library/Downloaded", "POST")] - public class ReportContentDownloaded : IReturnVoid - { - [ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ImageUrl { get; set; } - - [ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/Library/Downloading", "POST")] - public class ReportContentDownloading : IReturnVoid - { - [ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ImageUrl { get; set; } - - [ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - } /// /// Class LibraryStructureService /// + [Authenticated] public class LibraryStructureService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs index 62c7ac7c0..4fc3e00c0 100644 --- a/MediaBrowser.Api/Library/SubtitleService.cs +++ b/MediaBrowser.Api/Library/SubtitleService.cs @@ -1,6 +1,7 @@ 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; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } + [Authenticated] public class SubtitleService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index de01628f8..497f2d0a3 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -267,6 +268,7 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } + [Authenticated] public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index d3c47dfa1..5696d8df7 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using ServiceStack; @@ -42,6 +43,7 @@ namespace MediaBrowser.Api /// /// Class CulturesService /// + [Authenticated] public class LocalizationService : BaseApiService { /// diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index b9e3888fd..19e47eb85 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using ServiceStack; using System; @@ -45,6 +46,7 @@ namespace MediaBrowser.Api.Movies public Guid Id { get; set; } } + [Authenticated] public class CollectionService : BaseApiService { private readonly ICollectionManager _collectionManager; diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index db02ed428..f0bf22c5e 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -64,6 +65,7 @@ namespace MediaBrowser.Api.Movies /// /// Class MoviesService /// + [Authenticated] public class MoviesService : BaseApiService { /// diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 05e6a9577..b0ee6b6d5 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using ServiceStack; @@ -18,6 +19,7 @@ namespace MediaBrowser.Api.Movies /// /// Class TrailersService /// + [Authenticated] public class TrailersService : BaseApiService { /// diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 0732c951a..34a933dee 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -2,10 +2,10 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using ServiceStack; using System; -using System.Collections.Generic; using System.Linq; namespace MediaBrowser.Api.Music @@ -15,6 +15,7 @@ namespace MediaBrowser.Api.Music { } + [Authenticated] public class AlbumsService : BaseApiService { /// diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index f50c87f47..ff029d5b5 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using ServiceStack; using System.Collections.Generic; @@ -33,6 +34,7 @@ namespace MediaBrowser.Api.Music public string Name { get; set; } } + [Authenticated] public class InstantMixService : BaseApiService { private readonly IUserManager _userManager; diff --git a/MediaBrowser.Api/PackageReviewService.cs b/MediaBrowser.Api/PackageReviewService.cs index 94ff1b62e..112a2c5ce 100644 --- a/MediaBrowser.Api/PackageReviewService.cs +++ b/MediaBrowser.Api/PackageReviewService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Constants; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using ServiceStack; @@ -96,6 +97,7 @@ namespace MediaBrowser.Api } + [Authenticated] public class PackageReviewService : BaseApiService { private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index b54b05fcf..84b42baa3 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Updates; using ServiceStack; using System; @@ -121,6 +122,7 @@ namespace MediaBrowser.Api /// /// Class PackageService /// + [Authenticated] public class PackageService : BaseApiService { private readonly IInstallationManager _installationManager; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 9ff482a1a..75e13f92c 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Library; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using System; @@ -1955,12 +1954,6 @@ namespace MediaBrowser.Api.Playback /// The video request. private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest) { - // If enabled, allow whatever the client asks for - if (ServerConfigurationManager.Configuration.AllowVideoUpscaling) - { - return; - } - // Switch the incoming params to be ceilings rather than fixed values videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width; videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index fa78fa020..8a65e2b56 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -66,7 +66,6 @@ namespace MediaBrowser.Api.Playback.Hls return ProcessRequestAsync(request, isLive).Result; } - private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); /// /// Processes the request async. /// @@ -82,6 +81,11 @@ namespace MediaBrowser.Api.Playback.Hls var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); + if (isLive) + { + state.Request.StartTimeTicks = null; + } + var playlist = state.OutputFilePath; if (File.Exists(playlist)) @@ -90,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(playlist)) @@ -99,6 +103,8 @@ namespace MediaBrowser.Api.Playback.Hls } else { + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); + // If the playlist doesn't already exist, startup ffmpeg try { @@ -116,7 +122,7 @@ namespace MediaBrowser.Api.Playback.Hls } finally { - FfmpegStartLock.Release(); + ApiEntryPoint.Instance.TranscodingStartLock.Release(); } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 5bb610686..0af336c55 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -65,7 +65,6 @@ namespace MediaBrowser.Api.Playback.Hls return GetDynamicSegment(request).Result; } - private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private async Task GetDynamicSegment(GetDynamicHlsVideoSegment request) { if ((request.StartTimeTicks ?? 0) > 0) @@ -90,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Hls return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false); } - await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(segmentPath)) @@ -107,10 +106,11 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { + // TODO: Delete files from other jobs, but not this one + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.None, false).ConfigureAwait(false); + if (currentTranscodingIndex.HasValue) { - ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, playlistPath, FileDeleteMode.None); - DeleteLastFile(playlistPath, 0); } @@ -131,7 +131,7 @@ namespace MediaBrowser.Api.Playback.Hls } finally { - FfmpegStartLock.Release(); + ApiEntryPoint.Instance.TranscodingStartLock.Release(); } Logger.Info("waiting for {0}", segmentPath); diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index f31671b1a..3848cb2de 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -74,7 +74,9 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All); + var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); + + Task.WaitAll(task); } /// diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 31463dc3f..29cc7baf8 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -100,6 +101,7 @@ namespace MediaBrowser.Api /// /// Class PluginsService /// + [Authenticated] public class PluginService : BaseApiService { /// diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index aaf14ce71..483133c10 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Tasks; using ServiceStack; using ServiceStack.Text.Controller; @@ -78,6 +79,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class ScheduledTasksService /// + [Authenticated] public class ScheduledTaskService : BaseApiService { /// diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index e72edcc98..a6fcaf438 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Search; using ServiceStack; @@ -79,6 +80,7 @@ namespace MediaBrowser.Api /// /// Class SearchService /// + [Authenticated] public class SearchService : BaseApiService { /// diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 6776cbf17..c32e67216 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -175,6 +176,7 @@ namespace MediaBrowser.Api /// /// Class TvShowsService /// + [Authenticated] public class TvShowsService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 13027d30b..07015ecae 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class ArtistsService /// + [Authenticated] public class ArtistsService : BaseItemsByNameService { /// diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index b1379ad5d..49e979e55 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary public Guid? UserId { get; set; } } + [Authenticated] public class GameGenresService : BaseItemsByNameService { public GameGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 68f0e21ed..c8c45c1bb 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class GenresService /// + [Authenticated] public class GenresService : BaseItemsByNameService { public GenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index c926c16ff..ba07571bf 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -246,6 +247,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class ItemsService /// + [Authenticated] public class ItemsService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 538b16575..f50f0a0bd 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary public Guid? UserId { get; set; } } + [Authenticated] public class MusicGenresService : BaseItemsByNameService { public MusicGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 962effded..cf9959e53 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -51,6 +52,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class PersonsService /// + [Authenticated] public class PersonsService : BaseItemsByNameService { /// diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 4c38ba0d1..2024d779b 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class StudiosService /// + [Authenticated] public class StudiosService : BaseItemsByNameService { public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index da12a9e3d..d1767e7fd 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -424,6 +425,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class UserLibraryService /// + [Authenticated] public class UserLibraryService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index 4dda045c0..2b300f900 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class YearsService /// + [Authenticated] public class YearsService : BaseItemsByNameService { public YearsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 764a28102..64d1fcb34 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -268,28 +268,6 @@ namespace MediaBrowser.Api /// /// The request. public object Post(AuthenticateUser request) - { - // No response needed. Will throw an exception on failure. - var result = AuthenticateUser(request).Result; - - return result; - } - - public object Post(AuthenticateUserByName request) - { - var user = _userManager.Users.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); - - if (user == null) - { - throw new ArgumentException(string.Format("User {0} not found.", request.Username)); - } - - var result = AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).Result; - - return ToOptimizedResult(result); - } - - private async Task AuthenticateUser(AuthenticateUser request) { var user = _userManager.GetUserById(request.Id); @@ -298,38 +276,21 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - - // Login in the old way if the header is missing - if (string.IsNullOrEmpty(auth.Client) || - string.IsNullOrEmpty(auth.Device) || - string.IsNullOrEmpty(auth.DeviceId) || - string.IsNullOrEmpty(auth.Version)) + return Post(new AuthenticateUserByName { - var success = await _userManager.AuthenticateUser(user, request.Password).ConfigureAwait(false); - - if (!success) - { - // Unauthorized - throw new UnauthorizedAccessException("Invalid user or password entered."); - } - - return new AuthenticationResult - { - User = _dtoService.GetUserDto(user) - }; - } + Username = user.Name, + Password = request.Password + }); + } - var session = await _sessionMananger.AuthenticateNewSession(user, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp).ConfigureAwait(false); + public object Post(AuthenticateUserByName request) + { + var auth = AuthorizationContext.GetAuthorizationInfo(Request); - var result = new AuthenticationResult - { - User = _dtoService.GetUserDto(user), - SessionInfo = _sessionMananger.GetSessionInfoDto(session) - }; + var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, + auth.DeviceId, auth.Device, Request.RemoteIp).Result; - return result; + return ToOptimizedResult(result); } /// @@ -353,7 +314,7 @@ namespace MediaBrowser.Api } else { - var success = _userManager.AuthenticateUser(user, request.CurrentPassword).Result; + var success = _userManager.AuthenticateUser(user.Name, request.CurrentPassword).Result; if (!success) { diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index f62e37f79..2407a1a39 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Querying; using ServiceStack; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api public string Ids { get; set; } } + [Authenticated] public class VideosService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 010caa233..0da5f9272 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 diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index e609204d6..d7dcb60f0 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -28,5 +28,10 @@ namespace MediaBrowser.Controller.Net /// /// The version. public string Version { get; set; } + /// + /// Gets or sets the token. + /// + /// The token. + public string Token { get; set; } } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 74ad1b7ee..6d3a9d20c 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Users; using System; using System.Collections.Generic; using System.Threading; @@ -206,11 +207,11 @@ namespace MediaBrowser.Controller.Session /// The session identifier. /// The item. void ReportNowViewingItem(string sessionId, BaseItemInfo item); - + /// /// Authenticates the new session. /// - /// The user. + /// The username. /// The password. /// Type of the client. /// The application version. @@ -218,7 +219,7 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. - Task AuthenticateNewSession(User user, 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. @@ -248,5 +249,11 @@ namespace MediaBrowser.Controller.Session /// The version. /// SessionInfo. SessionInfo GetSession(string deviceId, string client, string version); + + /// + /// Validates the security token. + /// + /// The token. + void ValidateSecurityToken(string token); } } \ No newline at end of file diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 2d19d335d..0ed49d5f8 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -137,21 +137,9 @@ Configuration\MetadataPluginType.cs - - Configuration\NotificationOption.cs - - - Configuration\NotificationOptions.cs - - - Configuration\NotificationType.cs - Configuration\PathSubstitution.cs - - Configuration\SendToUserType.cs - Configuration\ServerConfiguration.cs @@ -638,6 +626,12 @@ Notifications\NotificationLevel.cs + + Notifications\NotificationOption.cs + + + Notifications\NotificationOptions.cs + Notifications\NotificationQuery.cs @@ -653,9 +647,15 @@ Notifications\NotificationsSummary.cs + + Notifications\NotificationType.cs + Notifications\NotificationTypeInfo.cs + + Notifications\SendToUserType.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index c0c886c8a..12c87ca97 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -124,21 +124,9 @@ Configuration\MetadataPluginType.cs - - Configuration\NotificationOption.cs - - - Configuration\NotificationOptions.cs - - - Configuration\NotificationType.cs - Configuration\PathSubstitution.cs - - Configuration\SendToUserType.cs - Configuration\ServerConfiguration.cs @@ -619,6 +607,12 @@ Notifications\NotificationLevel.cs + + Notifications\NotificationOption.cs + + + Notifications\NotificationOptions.cs + Notifications\NotificationQuery.cs @@ -634,9 +628,15 @@ Notifications\NotificationsSummary.cs + + Notifications\NotificationType.cs + Notifications\NotificationTypeInfo.cs + + Notifications\SendToUserType.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model/Configuration/NotificationOption.cs b/MediaBrowser.Model/Configuration/NotificationOption.cs deleted file mode 100644 index 5fcf3550c..000000000 --- a/MediaBrowser.Model/Configuration/NotificationOption.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class NotificationOption - { - public string Type { get; set; } - - /// - /// User Ids to not monitor (it's opt out) - /// - public string[] DisabledMonitorUsers { get; set; } - - /// - /// User Ids to send to (if SendToUserMode == Custom) - /// - public string[] SendToUsers { get; set; } - - /// - /// Gets or sets a value indicating whether this is enabled. - /// - /// true if enabled; otherwise, false. - public bool Enabled { get; set; } - - /// - /// Gets or sets the title format string. - /// - /// The title format string. - public string Title { get; set; } - - /// - /// Gets or sets the description. - /// - /// The description. - public string Description { get; set; } - - /// - /// Gets or sets the disabled services. - /// - /// The disabled services. - public string[] DisabledServices { get; set; } - - /// - /// Gets or sets the send to user mode. - /// - /// The send to user mode. - public SendToUserType SendToUserMode { get; set; } - - public NotificationOption() - { - DisabledServices = new string[] { }; - DisabledMonitorUsers = new string[] { }; - SendToUsers = new string[] { }; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/NotificationOptions.cs b/MediaBrowser.Model/Configuration/NotificationOptions.cs deleted file mode 100644 index dd523d947..000000000 --- a/MediaBrowser.Model/Configuration/NotificationOptions.cs +++ /dev/null @@ -1,124 +0,0 @@ -using MediaBrowser.Model.Extensions; - -namespace MediaBrowser.Model.Configuration -{ - public class NotificationOptions - { - public NotificationOption[] Options { get; set; } - - public NotificationOptions() - { - Options = new[] - { - new NotificationOption - { - Type = NotificationType.TaskFailed.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ServerRestartRequired.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginUpdateInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginUninstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.InstallationFailed.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginError.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - } - }; - } - - public NotificationOption GetOptions(string type) - { - foreach (NotificationOption i in Options) - { - if (StringHelper.EqualsIgnoreCase(type, i.Type)) return i; - } - return null; - } - - public bool IsEnabled(string type) - { - NotificationOption opt = GetOptions(type); - - return opt != null && opt.Enabled; - } - - public bool IsServiceEnabled(string service, string notificationType) - { - NotificationOption opt = GetOptions(notificationType); - - return opt == null || - !ListHelper.ContainsIgnoreCase(opt.DisabledServices, service); - } - - public bool IsEnabledToMonitorUser(string type, string userId) - { - NotificationOption opt = GetOptions(type); - - return opt != null && opt.Enabled && - !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); - } - - public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) - { - NotificationOption opt = GetOptions(type); - - if (opt != null && opt.Enabled) - { - if (opt.SendToUserMode == SendToUserType.All) - { - return true; - } - - if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) - { - return true; - } - - return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId); - } - - return false; - } - } -} diff --git a/MediaBrowser.Model/Configuration/NotificationType.cs b/MediaBrowser.Model/Configuration/NotificationType.cs deleted file mode 100644 index d8043b0e1..000000000 --- a/MediaBrowser.Model/Configuration/NotificationType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public enum NotificationType - { - ApplicationUpdateAvailable, - ApplicationUpdateInstalled, - AudioPlayback, - GamePlayback, - VideoPlayback, - AudioPlaybackStopped, - GamePlaybackStopped, - VideoPlaybackStopped, - InstallationFailed, - PluginError, - PluginInstalled, - PluginUpdateInstalled, - PluginUninstalled, - NewLibraryContent, - NewLibraryContentMultiple, - ServerRestartRequired, - TaskFailed - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/SendToUserType.cs b/MediaBrowser.Model/Configuration/SendToUserType.cs deleted file mode 100644 index a2eac4c2d..000000000 --- a/MediaBrowser.Model/Configuration/SendToUserType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public enum SendToUserType - { - All = 0, - Admins = 1, - Custom = 2 - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f7b888f82..36c353479 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Weather; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Weather; using System; namespace MediaBrowser.Model.Configuration @@ -178,8 +179,6 @@ namespace MediaBrowser.Model.Configuration /// The encoding quality. public EncodingQuality MediaEncodingQuality { get; set; } - public bool AllowVideoUpscaling { get; set; } - public MetadataOptions[] MetadataOptions { get; set; } public bool EnableDebugEncodingLogging { get; set; } @@ -268,10 +267,7 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - NotificationOptions = new NotificationOptions(); - SubtitleOptions = new SubtitleOptions(); - LiveTvOptions = new LiveTvOptions(); TvFileOrganizationOptions = new TvFileOrganizationOptions(); } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index d2b0a8caf..d758f2f39 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -88,11 +88,11 @@ - - - + + + - + diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs new file mode 100644 index 000000000..09f7072dd --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationOption.cs @@ -0,0 +1,56 @@ +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Model.Notifications +{ + public class NotificationOption + { + public string Type { get; set; } + + /// + /// User Ids to not monitor (it's opt out) + /// + public string[] DisabledMonitorUsers { get; set; } + + /// + /// User Ids to send to (if SendToUserMode == Custom) + /// + public string[] SendToUsers { get; set; } + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// true if enabled; otherwise, false. + public bool Enabled { get; set; } + + /// + /// Gets or sets the title format string. + /// + /// The title format string. + public string Title { get; set; } + + /// + /// Gets or sets the description. + /// + /// The description. + public string Description { get; set; } + + /// + /// Gets or sets the disabled services. + /// + /// The disabled services. + public string[] DisabledServices { get; set; } + + /// + /// Gets or sets the send to user mode. + /// + /// The send to user mode. + public SendToUserType SendToUserMode { get; set; } + + public NotificationOption() + { + DisabledServices = new string[] { }; + DisabledMonitorUsers = new string[] { }; + SendToUsers = new string[] { }; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs new file mode 100644 index 000000000..7d80f3177 --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -0,0 +1,125 @@ +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Extensions; + +namespace MediaBrowser.Model.Notifications +{ + public class NotificationOptions + { + public NotificationOption[] Options { get; set; } + + public NotificationOptions() + { + Options = new[] + { + new NotificationOption + { + Type = NotificationType.TaskFailed.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ServerRestartRequired.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ApplicationUpdateAvailable.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ApplicationUpdateInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginUpdateInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginUninstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.InstallationFailed.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginError.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + } + }; + } + + public NotificationOption GetOptions(string type) + { + foreach (NotificationOption i in Options) + { + if (StringHelper.EqualsIgnoreCase(type, i.Type)) return i; + } + return null; + } + + public bool IsEnabled(string type) + { + NotificationOption opt = GetOptions(type); + + return opt != null && opt.Enabled; + } + + public bool IsServiceEnabled(string service, string notificationType) + { + NotificationOption opt = GetOptions(notificationType); + + return opt == null || + !ListHelper.ContainsIgnoreCase(opt.DisabledServices, service); + } + + public bool IsEnabledToMonitorUser(string type, string userId) + { + NotificationOption opt = GetOptions(type); + + return opt != null && opt.Enabled && + !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); + } + + public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) + { + NotificationOption opt = GetOptions(type); + + if (opt != null && opt.Enabled) + { + if (opt.SendToUserMode == SendToUserType.All) + { + return true; + } + + if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) + { + return true; + } + + return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId); + } + + return false; + } + } +} diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs new file mode 100644 index 000000000..34e4d32b5 --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Notifications +{ + public enum NotificationType + { + ApplicationUpdateAvailable, + ApplicationUpdateInstalled, + AudioPlayback, + GamePlayback, + VideoPlayback, + AudioPlaybackStopped, + GamePlaybackStopped, + VideoPlaybackStopped, + InstallationFailed, + PluginError, + PluginInstalled, + PluginUpdateInstalled, + PluginUninstalled, + NewLibraryContent, + NewLibraryContentMultiple, + ServerRestartRequired, + TaskFailed + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Notifications/SendToUserType.cs b/MediaBrowser.Model/Notifications/SendToUserType.cs new file mode 100644 index 000000000..1998d3102 --- /dev/null +++ b/MediaBrowser.Model/Notifications/SendToUserType.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Model.Notifications +{ + public enum SendToUserType + { + All = 0, + Admins = 1, + Custom = 2 + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs index de8f4e8b8..b9bf6e7fe 100644 --- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs +++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs @@ -1,4 +1,6 @@ - +using System; +using System.Runtime.Serialization; + namespace MediaBrowser.Model.Updates { /// @@ -24,6 +26,32 @@ namespace MediaBrowser.Model.Updates /// The version STR. public string versionStr { get; set; } + /// + /// The _version + /// + private Version _version; + /// + /// Gets or sets the version. + /// Had to make this an interpreted property since Protobuf can't handle Version + /// + /// The version. + [IgnoreDataMember] + public Version version + { + get { return _version ?? (_version = new Version(ValueOrDefault(versionStr, "0.0.0.1"))); } + } + + /// + /// Values the or default. + /// + /// The STR. + /// The def. + /// System.String. + private static string ValueOrDefault(string str, string def) + { + return string.IsNullOrEmpty(str) ? def : str; + } + /// /// Gets or sets the classification. /// @@ -60,4 +88,4 @@ namespace MediaBrowser.Model.Updates /// The target filename. public string targetFilename { get; set; } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index 998aaa3a7..8046e83c7 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -16,5 +16,11 @@ namespace MediaBrowser.Model.Users /// /// The session information. public SessionInfoDto SessionInfo { get; set; } + + /// + /// Gets or sets the authentication token. + /// + /// The authentication token. + public string AuthenticationToken { get; set; } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 1a7f9db28..c29a7d14e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -51,13 +51,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security private void ValidateUser(IRequest req) { - User user = null; - //This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(req); - if (auth != null) + if (string.IsNullOrWhiteSpace(auth.Token)) { + // Legacy + // TODO: Deprecate this in Oct 2014 + + User user = null; + if (!string.IsNullOrWhiteSpace(auth.UserId)) { var userId = auth.UserId; @@ -65,22 +68,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security user = UserManager.GetUserById(new Guid(userId)); } - string deviceId = auth.DeviceId; - string device = auth.Device; - string client = auth.Client; - string version = auth.Version; - - if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + if (user == null || user.Configuration.IsDisabled) { - var remoteEndPoint = req.RemoteIp; - - SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + throw new UnauthorizedAccessException("Unauthorized access."); } } - - if (user == null || user.Configuration.IsDisabled) + else { - throw new UnauthorizedAccessException("Unauthorized access."); + SessionManager.ValidateSecurityToken(auth.Token); } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 6ea77f251..77343ab4e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security Device = device, DeviceId = deviceId, UserId = userId, - Version = version + Version = version, + Token = httpReq.Headers["X-AUTH-TOKEN"] }; } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index f9d7479ce..1e0bbc39b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -121,17 +121,20 @@ namespace MediaBrowser.Server.Implementations.Library /// /// Authenticates a User and returns a result indicating whether or not it succeeded /// - /// The user. + /// The username. /// The password. /// Task{System.Boolean}. /// user - public async Task AuthenticateUser(User user, string password) + /// + public async Task AuthenticateUser(string username, string password) { - if (user == null) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException("username"); } + var user = Users.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + if (user.Configuration.IsDisabled) { throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 467bedcf1..ad1ddba88 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly List _services = new List(); - private readonly ConcurrentDictionary _openStreams = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _openStreams = + new ConcurrentDictionary(); private List _channelIdList = new List(); private Dictionary _programs = new Dictionary(); @@ -291,66 +291,69 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); public async Task GetRecordingStream(string id, CancellationToken cancellationToken) + { + return await GetLiveStream(id, false, cancellationToken).ConfigureAwait(false); + } + + public async Task GetChannelStream(string id, CancellationToken cancellationToken) + { + return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false); + } + + private async Task GetLiveStream(string id, bool isChannel, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var service = ActiveService; - - var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); - - var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); - - var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false); - - Sanitize(result); + // Avoid implicitly captured closure + var itemId = id; - _logger.Debug("Live stream info: " + _json.SerializeToString(result)); + var stream = _openStreams + .Where(i => string.Equals(i.Value.ItemId, itemId) && isChannel == i.Value.IsChannel) + .Take(1) + .Select(i => i.Value) + .FirstOrDefault(); - if (!string.IsNullOrEmpty(result.Id)) + if (stream != null) { - _openStreams.AddOrUpdate(result.Id, result, (key, info) => result); + stream.ConsumerCount++; + _logger.Debug("Returning existing live tv stream"); + return stream.Info; } - return result; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting recording stream", ex); - - throw; - } - finally - { - _liveStreamSemaphore.Release(); - } - } - - public async Task GetChannelStream(string id, CancellationToken cancellationToken) - { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { var service = ActiveService; + LiveStreamInfo info; - var channel = GetInternalChannel(id); - - _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); + if (isChannel) + { + var channel = GetInternalChannel(id); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); + info = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); + } + else + { + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); - Sanitize(result); + _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.Id); + info = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false); + } - _logger.Debug("Live stream info: " + _json.SerializeToString(result)); + Sanitize(info); - if (!string.IsNullOrEmpty(result.Id)) + var data = new LiveStreamData { - _openStreams.AddOrUpdate(result.Id, result, (key, info) => result); - } + Info = info, + ConsumerCount = 1, + IsChannel = isChannel, + ItemId = id + }; - return result; + _openStreams.AddOrUpdate(info.Id, data, (key, i) => data); + + return info; } catch (Exception ex) { @@ -1597,20 +1600,38 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } + class LiveStreamData + { + internal LiveStreamInfo Info; + internal int ConsumerCount; + internal string ItemId; + internal bool IsChannel; + } + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - var service = ActiveService; - - _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); - try { - await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); + var service = ActiveService; + + LiveStreamData data; + if (_openStreams.TryGetValue(id, out data)) + { + if (data.ConsumerCount > 1) + { + data.ConsumerCount--; + _logger.Info("Decrementing live stream client count."); + return; + } - LiveStreamInfo removed; - _openStreams.TryRemove(id, out removed); + } + _openStreams.TryRemove(id, out data); + + _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); + + await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -1662,7 +1683,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { foreach (var stream in _openStreams.Values.ToList()) { - var task = CloseLiveStream(stream.Id, CancellationToken.None); + var task = CloseLiveStream(stream.Info.Id, CancellationToken.None); Task.WaitAll(task); } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index b3280dac7..41555fe82 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -201,5 +201,21 @@ "ButtonOpenInNewTab": "Open in new tab", "ButtonShuffle": "Shuffle", "ButtonInstantMix": "Instant mix", - "ButtonResume": "Resume" + "ButtonResume": "Resume", + "HeaderScenes": "Scenes", + "HeaderAudioTracks": "Audio Tracks", + "LabelUnknownLanguage": "Unknown language", + "HeaderSubtitles": "Subtitles", + "HeaderVideoQuality": "Video Quality", + "MessageErrorPlayingVideo": "There was an error playing the video.", + "MessageEnsureOpenTuner": "Please ensure there is an open tuner availalble.", + "ButtonHome": "Home", + "ButtonDashboard": "Dashboard", + "ButtonReports": "Reports", + "ButtonMetadataManager": "Metadata Manager", + "HeaderTime": "Time", + "HeaderName": "Name", + "HeaderAlbum": "Album", + "HeaderAlbumArtist": "Album Artist", + "HeaderArtist": "Artist" } \ 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 10e27bc30..a909929ae 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -200,6 +200,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs new file mode 100644 index 000000000..a336eba0e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Notifications; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Notifications +{ + public class NotificationConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new List + { + new ConfigurationStore + { + Key = "notifications", + ConfigurationType = typeof (NotificationOptions) + } + }; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 416b29e86..964e2cd24 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -30,13 +31,18 @@ namespace MediaBrowser.Server.Implementations.Notifications _logger = logManager.GetLogger(GetType().Name); } + private NotificationOptions GetConfiguration() + { + return _config.GetConfiguration("notifications"); + } + public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken) { var notificationType = request.NotificationType; var options = string.IsNullOrWhiteSpace(notificationType) ? null : - _config.Configuration.NotificationOptions.GetOptions(notificationType); + GetConfiguration().GetOptions(notificationType); var users = GetUserIds(request, options) .Except(request.ExcludeUserIds) @@ -223,7 +229,7 @@ namespace MediaBrowser.Server.Implementations.Notifications private bool IsEnabled(INotificationService service, string notificationType) { return string.IsNullOrEmpty(notificationType) || - _config.Configuration.NotificationOptions.IsServiceEnabled(service.Name, notificationType); + GetConfiguration().IsServiceEnabled(service.Name, notificationType); } public void AddParts(IEnumerable services, IEnumerable notificationTypeFactories) @@ -248,9 +254,11 @@ namespace MediaBrowser.Server.Implementations.Notifications }).SelectMany(i => i).ToList(); + var config = GetConfiguration(); + foreach (var i in list) { - i.Enabled = _config.Configuration.NotificationOptions.IsEnabled(i.Type); + i.Enabled = config.IsEnabled(i.Type); } return list; diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 4e2b3c7b7..2f6790a3e 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -25,6 +25,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Users; namespace MediaBrowser.Server.Implementations.Session { @@ -1138,10 +1139,15 @@ namespace MediaBrowser.Server.Implementations.Session } } + public void ValidateSecurityToken(string token) + { + + } + /// /// Authenticates the new session. /// - /// The user. + /// The username. /// The password. /// Type of the client. /// The application version. @@ -1149,17 +1155,34 @@ namespace MediaBrowser.Server.Implementations.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. + /// Invalid user or password entered. /// - public async Task AuthenticateNewSession(User user, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) + public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) { - var result = await _userManager.AuthenticateUser(user, password).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { throw new UnauthorizedAccessException("Invalid user or password entered."); } - return await LogSessionActivity(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + var user = _userManager.Users + .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var session = await LogSessionActivity(clientType, + appVersion, + deviceId, + deviceName, + remoteEndPoint, + user) + .ConfigureAwait(false); + + return new AuthenticationResult + { + User = _dtoService.GetUserDto(user), + SessionInfo = GetSessionInfoDto(session), + AuthenticationToken = Guid.NewGuid().ToString("N") + }; } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e8fb7bfcb..673a9f151 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -318,6 +318,13 @@ namespace MediaBrowser.ServerApplication saveConfig = true; } + if (ServerConfigurationManager.Configuration.NotificationOptions != null) + { + ServerConfigurationManager.SaveConfiguration("notifications", ServerConfigurationManager.Configuration.NotificationOptions); + ServerConfigurationManager.Configuration.NotificationOptions = null; + saveConfig = true; + } + if (saveConfig) { ServerConfigurationManager.SaveConfiguration(); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 80f864a32..3ccf26c37 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -403,7 +403,7 @@ namespace MediaBrowser.WebDashboard.Api var files = new[] { - "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.css", + "thirdparty/jquerymobile-1.4.3/jquery.mobile-1.4.3.min.css", "css/all.css" + versionString }; @@ -449,7 +449,7 @@ namespace MediaBrowser.WebDashboard.Api // jQuery + jQuery mobile await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false); - await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.js", newLineBytes).ConfigureAwait(false); + await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.3/jquery.mobile-1.4.3.min.js", newLineBytes).ConfigureAwait(false); await AppendLocalization(memoryStream).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index cbdae6d08..8f85fcb51 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -815,613 +815,613 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -2144,6 +2144,9 @@ PreserveNewest + + PreserveNewest + Designer -- 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.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs new file mode 100644 index 000000000..4b49b411a --- /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 39fcc50d8..291deb3b0 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 4f8c348bb..deaefe019 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 b95e18a0d..993b69601 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 77a714755..10aa23126 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 4fc3e00c0..000000000 --- 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 a68966b33..ca2887d19 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 28edb61dd..51a080106 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 00c307a18..f4651601b 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 000000000..3e692cb22 --- /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 2f0741434..6f2e83a79 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 d1767e7fd..55cdc8681 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 64d1fcb34..cda489c94 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 46dabb042..000000000 --- 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 0cc4fc6b4..dcac5e7ba 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 f9d7cc21a..0d0555dc0 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 524d7097b..042834731 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 584091b13..b886cef19 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 780181a61..d576d90c4 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 b9630a66f..847183fd0 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 e5a8135c2..6404e71ec 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 86099fdc0..619a497f5 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 2bec9e3de..9db91e7f2 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 23610351e..ba1cb3043 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 692d6db90..7f1ddbce9 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 6d3a9d20c..7b2062182 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 0ed49d5f8..91c1508fc 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 12c87ca97..782e8524d 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 000000000..737cb5c48 --- /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 36c353479..49b731341 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 db8b69951..86eb40b97 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 26b0e9d9e..6ee9f1916 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 d758f2f39..4d4ca8e20 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 f511d41a9..6e9368f44 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 40723eff8..98df3efe5 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 000000000..ba409c542 --- /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 12af93dbd..1b2e9fa6d 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 92b4616e7..57a40741f 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 000000000..d6cd3424b --- /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 73216ca33..62ff9f687 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 d7186aa21..c31f46215 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 833dfc5e4..cfcbb077e 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 77343ab4e..94be37e95 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 79f126511..d3030f31f 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 412b2e7bd..9c69e656d 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 ad1ddba88..09793f4fc 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 3bd0df0eb..d6d4bbc51 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 a909929ae..1d201e069 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 964e2cd24..b832f3a06 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 2f6790a3e..2d85a3aa7 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 46c3df07b..7e6a252cd 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 673a9f151..91e92e21c 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 4ba98e9b6..c2c64ea4d 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 f5f9434e7..47a4be8e3 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 3ccf26c37..36adae71c 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 8f85fcb51..0099daaf1 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 c02e917f5657db4bd76fc6ca17c535fc441c641c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 7 Jul 2014 21:41:03 -0400 Subject: completed auth database --- MediaBrowser.Api/ApiEntryPoint.cs | 34 +-- MediaBrowser.Api/ConfigurationService.cs | 1 + MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 9 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 3 +- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 2 +- MediaBrowser.Api/SessionsService.cs | 18 +- MediaBrowser.Api/SystemService.cs | 1 + MediaBrowser.Api/UserService.cs | 32 +- .../Channels/ChannelItemInfo.cs | 13 +- .../Collections/CollectionEvents.cs | 37 +++ .../Collections/ICollectionManager.cs | 15 + MediaBrowser.Controller/Dto/IDtoService.cs | 7 + .../MediaBrowser.Controller.csproj | 4 + .../Providers/IMetadataProvider.cs | 1 + .../Security/AuthenticationInfo.cs | 61 ++++ .../Security/AuthenticationInfoQuery.cs | 42 +++ .../Security/IAuthenticationRepository.cs | 39 +++ MediaBrowser.Controller/Session/ISessionManager.cs | 25 +- MediaBrowser.Controller/Session/SessionInfo.cs | 2 +- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 10 +- MediaBrowser.Model/ApiClient/IApiClient.cs | 19 +- .../Configuration/ServerConfiguration.cs | 2 + MediaBrowser.Model/Users/AuthenticationResult.cs | 2 +- MediaBrowser.Providers/Manager/MetadataService.cs | 1 - MediaBrowser.Providers/Manager/ProviderUtils.cs | 2 +- .../TV/MovieDbEpisodeImageProvider.cs | 2 +- .../Channels/ChannelManager.cs | 3 +- .../Collections/CollectionManager.cs | 57 +++- .../HttpServer/Security/AuthService.cs | 48 +-- .../MediaBrowser.Server.Implementations.csproj | 1 + .../SqliteFileOrganizationRepository.cs | 2 +- .../Security/AuthenticationRepository.cs | 338 +++++++++++++++++++++ .../Session/SessionManager.cs | 159 +++++++++- MediaBrowser.ServerApplication/ApplicationHost.cs | 20 +- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 2 +- .../Providers/BaseNfoProvider.cs | 10 +- MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs | 13 +- .../Savers/XmlSaverHelpers.cs | 22 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 41 files changed, 933 insertions(+), 136 deletions(-) create mode 100644 MediaBrowser.Controller/Collections/CollectionEvents.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfo.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs create mode 100644 MediaBrowser.Controller/Security/IAuthenticationRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 8e6ca4401..f255339bc 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; - public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); + public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); /// /// Initializes a new instance of the class. @@ -102,7 +102,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, FileDeleteMode.All)); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -295,17 +295,18 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; - KillTranscodingJob(job, FileDeleteMode.All); + KillTranscodingJob(job, path => true); } /// /// Kills the single transcoding job. /// /// The device id. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// sourcePath - internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -330,12 +331,12 @@ namespace MediaBrowser.Api { await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); } - + try { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -352,10 +353,11 @@ namespace MediaBrowser.Api /// /// The device identifier. /// The type. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// deviceId - internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -385,7 +387,7 @@ namespace MediaBrowser.Api { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -401,8 +403,8 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// /// The job. - /// The delete mode. - private void KillTranscodingJob(TranscodingJob job, FileDeleteMode deleteMode) + /// The delete. + private void KillTranscodingJob(TranscodingJob job, Func delete) { lock (_activeTranscodingJobs) { @@ -454,7 +456,7 @@ namespace MediaBrowser.Api } } - if (deleteMode == FileDeleteMode.All) + if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } @@ -593,10 +595,4 @@ namespace MediaBrowser.Api /// Hls } - - public enum FileDeleteMode - { - None, - All - } } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index e628c5f7a..6710461ad 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetConfiguration /// [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] + [Authenticated] public class GetConfiguration : IReturn { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 8a65e2b56..8eff75533 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -22,7 +22,8 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } @@ -103,8 +104,8 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); - + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => true, false).ConfigureAwait(false); + // If the playlist doesn't already exist, startup ffmpeg try { @@ -252,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; - + var itsOffsetMs = hlsVideoRequest == null ? 0 : hlsVideoRequest.TimeStampOffsetMs; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 6c09f00a1..07aaf5f86 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -127,8 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - // TODO: Delete files from other jobs, but not this one - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.None, false).ConfigureAwait(false); + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 3848cb2de..f28352588 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); + var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true); Task.WaitAll(task); } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index f4651601b..ed7db626f 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -213,6 +213,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] public class PostCapabilities : IReturnVoid { /// @@ -235,6 +236,11 @@ namespace MediaBrowser.Api public bool SupportsMediaControl { get; set; } } + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + /// /// Class SessionsService /// @@ -246,16 +252,26 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; /// /// Initializes a new instance of the class. /// /// The session manager. /// The user manager. - public SessionsService(ISessionManager sessionManager, IUserManager userManager) + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext) { _sessionManager = sessionManager; _userManager = userManager; + _authContext = authContext; + } + + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); } /// diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 6f2e83a79..f336736be 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Api /// Class GetSystemInfo /// [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] public class GetSystemInfo : IReturn { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index cda489c94..bcaf80d69 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using ServiceStack; using ServiceStack.Text.Controller; @@ -19,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetUsers /// [Route("/Users", "GET", Summary = "Gets a list of users")] + [Authenticated] public class GetUsers : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -37,6 +37,7 @@ namespace MediaBrowser.Api /// Class GetUser /// [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] + [Authenticated] public class GetUser : IReturn { /// @@ -159,11 +160,6 @@ namespace MediaBrowser.Api /// public class UserService : BaseApiService, IHasAuthorization { - /// - /// The _XML serializer - /// - private readonly IXmlSerializer _xmlSerializer; - /// /// The _user manager /// @@ -176,19 +172,12 @@ namespace MediaBrowser.Api /// /// Initializes a new instance of the class. /// - /// The XML serializer. /// The user manager. /// The dto service. + /// The session mananger. /// xmlSerializer - public UserService(IXmlSerializer xmlSerializer, IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) - : base() + public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) { - if (xmlSerializer == null) - { - throw new ArgumentNullException("xmlSerializer"); - } - - _xmlSerializer = xmlSerializer; _userManager = userManager; _dtoService = dtoService; _sessionMananger = sessionMananger; @@ -196,6 +185,11 @@ namespace MediaBrowser.Api public object Get(GetPublicUsers request) { + if (!Request.IsLocal && !_sessionMananger.IsLocal(Request.RemoteIp)) + { + return ToOptimizedResult(new List()); + } + return Get(new GetUsers { IsHidden = false, @@ -368,9 +362,15 @@ namespace MediaBrowser.Api { throw new ArgumentException("There must be at least one enabled user in the system."); } + + var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); + + Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : + _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index 707807bd5..3c7df91c1 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Channels { public string Name { get; set; } + public string SeriesName { get; set; } + public string Id { get; set; } public ChannelItemType Type { get; set; } @@ -28,8 +30,6 @@ namespace MediaBrowser.Controller.Channels public long? RunTimeTicks { get; set; } - public bool IsInfiniteStream { get; set; } - public string ImageUrl { get; set; } public ChannelMediaType MediaType { get; set; } @@ -43,9 +43,14 @@ namespace MediaBrowser.Controller.Channels public int? ProductionYear { get; set; } public DateTime? DateCreated { get; set; } - + + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } + public List MediaSources { get; set; } - + + public bool IsInfiniteStream { get; set; } + public ChannelItemInfo() { MediaSources = new List(); diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionEvents.cs new file mode 100644 index 000000000..80f66a444 --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionEvents.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } + + public class CollectionModifiedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the items changed. + /// + /// The items changed. + public List ItemsChanged { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index fdb2a4975..9130f68d4 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -8,6 +8,21 @@ namespace MediaBrowser.Controller.Collections { public interface ICollectionManager { + /// + /// Occurs when [collection created]. + /// + event EventHandler CollectionCreated; + + /// + /// Occurs when [items added to collection]. + /// + event EventHandler ItemsAddedToCollection; + + /// + /// Occurs when [items removed from collection]. + /// + event EventHandler ItemsRemovedFromCollection; + /// /// Creates the collection. /// diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 0d0555dc0..434e896da 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -50,6 +50,13 @@ namespace MediaBrowser.Controller.Dto /// ChapterInfoDto. ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item); + /// + /// Gets the user item data dto. + /// + /// The data. + /// UserItemDataDto. + UserItemDataDto GetUserItemDataDto(UserItemData data); + /// /// Gets the item by name dto. /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ad3033f9..1c60ea8e6 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -95,6 +95,7 @@ + @@ -233,6 +234,9 @@ + + + diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index d33b2c9eb..52cd6fcea 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs new file mode 100644 index 000000000..dd5eec1f9 --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -0,0 +1,61 @@ +using System; + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfo + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string AppName { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string DeviceName { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// true if this instance is active; otherwise, false. + public bool IsActive { get; set; } + + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date revoked. + /// + /// The date revoked. + public DateTime? DateRevoked { get; set; } + } +} diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs new file mode 100644 index 000000000..3234b0350 --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs @@ -0,0 +1,42 @@ + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfoQuery + { + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// null if [is active] contains no value, true if [is active]; otherwise, false. + public bool? IsActive { get; set; } + + /// + /// 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.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs new file mode 100644 index 000000000..219b07028 --- /dev/null +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -0,0 +1,39 @@ +using MediaBrowser.Model.Querying; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Security +{ + public interface IAuthenticationRepository + { + /// + /// Creates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Create(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Updates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Update(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Gets the specified query. + /// + /// The query. + /// QueryResult{AuthenticationInfo}. + QueryResult Get(AuthenticationInfoQuery query); + + /// + /// Gets the specified identifier. + /// + /// The identifier. + /// AuthenticationInfo. + AuthenticationInfo Get(string id); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 7b2062182..4b30c964c 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -259,7 +259,28 @@ namespace MediaBrowser.Controller.Session /// /// Validates the security token. /// - /// The token. - void ValidateSecurityToken(string token); + /// The access token. + void ValidateSecurityToken(string accessToken); + + /// + /// Logouts the specified access token. + /// + /// The access token. + /// Task. + Task Logout(string accessToken); + + /// + /// Revokes the user tokens. + /// + /// The user identifier. + /// Task. + Task RevokeUserTokens(string userId); + + /// + /// Determines whether the specified remote endpoint is local. + /// + /// The remote endpoint. + /// true if the specified remote endpoint is local; otherwise, false. + bool IsLocal(string remoteEndpoint); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6f27f6cb2..58d455955 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Session public List SupportedCommands { get; set; } public TranscodingInfo TranscodingInfo { get; set; } - + /// /// Gets a value indicating whether this instance is active. /// diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index cc9bc7bed..62aec5ecb 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -1,11 +1,11 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.LocalMetadata { diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 33c6f980c..74a3314b7 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -246,7 +246,7 @@ namespace MediaBrowser.Model.ApiClient /// Gets the client session asynchronous. /// /// Task{SessionInfoDto}. - Task GetCurrentSessionAsync(); + Task GetCurrentSessionAsync(CancellationToken cancellationToken); /// /// Gets the item counts async. @@ -644,6 +644,13 @@ namespace MediaBrowser.Model.ApiClient /// Task. Task SetVolume(string sessionId, int volume); + /// + /// Stops the transcoding processes. + /// + /// The device identifier. + /// Task. + Task StopTranscodingProcesses(string deviceId); + /// /// Sets the index of the audio stream. /// @@ -984,7 +991,7 @@ namespace MediaBrowser.Model.ApiClient /// The query. /// The cancellation token. /// Task{LiveTvInfo}. - Task> GetLiveTvChannelsAsync(ChannelQuery query, CancellationToken cancellationToken); + Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken); /// /// Gets the live tv channel asynchronous. @@ -1187,5 +1194,13 @@ namespace MediaBrowser.Model.ApiClient /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken); + + /// + /// Gets the latest channel items. + /// + /// The query. + /// The cancellation token. + /// Task{QueryResult{BaseItemDto}}. + Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 49b731341..d9404ce29 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.Model.Configuration public bool DefaultMetadataSettingsApplied { get; set; } + public bool EnableTokenAuthentication { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index 8046e83c7..97fe2ea99 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -21,6 +21,6 @@ namespace MediaBrowser.Model.Users /// Gets or sets the authentication token. /// /// The authentication token. - public string AuthenticationToken { get; set; } + public string AccessToken { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 95eca6ba0..7feca2d34 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 2783fda6b..f09890c40 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Manager var key = id.Key; // Don't replace existing Id's. - if (!target.ProviderIds.ContainsKey(key)) + if (replaceData || !target.ProviderIds.ContainsKey(key)) { target.ProviderIds[key] = id.Value; } diff --git a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs index 7979711ec..b3f62005b 100644 --- a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs @@ -19,7 +19,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder + public class MovieDbEpisodeImageProvider/* : IRemoteImageProvider, IHasOrder*/ { private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index c6dd80758..d0ea64e0f 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1133,6 +1133,8 @@ namespace MediaBrowser.Server.Implementations.Channels item.CommunityRating = info.CommunityRating; item.OfficialRating = info.OfficialRating; item.Overview = info.Overview; + item.IndexNumber = info.IndexNumber; + item.ParentIndexNumber = info.ParentIndexNumber; item.People = info.People; item.PremiereDate = info.PremiereDate; item.ProductionYear = info.ProductionYear; @@ -1159,7 +1161,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (channelMediaItem != null) { - channelMediaItem.IsInfiniteStream = info.IsInfiniteStream; channelMediaItem.ContentType = info.ContentType; channelMediaItem.ChannelMediaSources = info.MediaSources; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 728b18bbf..7bc7838c6 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; using MoreLinq; using System; using System.Collections.Generic; @@ -19,12 +21,18 @@ namespace MediaBrowser.Server.Implementations.Collections private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger _logger; - public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) + public event EventHandler CollectionCreated; + public event EventHandler ItemsAddedToCollection; + public event EventHandler ItemsRemovedFromCollection; + + public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILogger logger) { _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; + _logger = logger; } public Folder GetCollectionsFolder(string userId) @@ -74,9 +82,16 @@ namespace MediaBrowser.Server.Implementations.Collections if (options.ItemIdList.Count > 0) { - await AddToCollection(collection.Id, options.ItemIdList); + await AddToCollection(collection.Id, options.ItemIdList, false); } + EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs + { + Collection = collection, + Options = options + + }, _logger); + return collection; } finally @@ -113,7 +128,12 @@ namespace MediaBrowser.Server.Implementations.Collections return GetCollectionsFolder(string.Empty); } - public async Task AddToCollection(Guid collectionId, IEnumerable ids) + public Task AddToCollection(Guid collectionId, IEnumerable ids) + { + return AddToCollection(collectionId, ids, true); + } + + private async Task AddToCollection(Guid collectionId, IEnumerable ids, bool fireEvent) { var collection = _libraryManager.GetItemById(collectionId) as BoxSet; @@ -123,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); var currentLinkedChildren = collection.GetLinkedChildren().ToList(); foreach (var itemId in ids) @@ -134,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("No item exists with the supplied Id"); } + itemList.Add(item); + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); @@ -165,6 +188,16 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + if (fireEvent) + { + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); + } } public async Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds) @@ -177,6 +210,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); foreach (var itemId in itemIds) { @@ -190,6 +224,12 @@ namespace MediaBrowser.Server.Implementations.Collections list.Add(child); var childItem = _libraryManager.GetItemById(itemId); + + if (childItem != null) + { + itemList.Add(childItem); + } + var supportsGrouping = childItem as ISupportsBoxSetGrouping; if (supportsGrouping != null) @@ -221,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Collections { File.Delete(file); } - + foreach (var child in list) { collection.LinkedChildren.Remove(child); @@ -238,6 +278,13 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); } public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index c29a7d14e..6894d7ac7 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; @@ -13,9 +13,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { - public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext) + private readonly IServerConfigurationManager _config; + + public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config) { AuthorizationContext = authorizationContext; + _config = config; SessionManager = sessionManager; UserManager = userManager; } @@ -54,28 +57,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security //This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(req); - if (string.IsNullOrWhiteSpace(auth.Token)) + if (!string.IsNullOrWhiteSpace(auth.Token) || _config.Configuration.EnableTokenAuthentication) { - // Legacy - // TODO: Deprecate this in Oct 2014 - - User user = null; - - if (!string.IsNullOrWhiteSpace(auth.UserId)) - { - var userId = auth.UserId; + SessionManager.ValidateSecurityToken(auth.Token); + } - user = UserManager.GetUserById(new Guid(userId)); - } + var user = string.IsNullOrWhiteSpace(auth.UserId) + ? null + : UserManager.GetUserById(new Guid(auth.UserId)); - if (user == null || user.Configuration.IsDisabled) - { - throw new UnauthorizedAccessException("Unauthorized access."); - } + if (user != null && user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); } - else + + if (!string.IsNullOrWhiteSpace(auth.DeviceId) && + !string.IsNullOrWhiteSpace(auth.Client) && + !string.IsNullOrWhiteSpace(auth.Device)) { - SessionManager.ValidateSecurityToken(auth.Token); + SessionManager.LogSessionActivity(auth.Client, + auth.Version, + auth.DeviceId, + auth.Device, + req.RemoteIp, + user); } } @@ -108,11 +113,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security } } - private void LogRequest() - { - - } - protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) { var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 1d201e069..859011f6e 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -216,6 +216,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs index df32ac021..5d5855bf8 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { - public class SqliteFileOrganizationRepository : IFileOrganizationRepository + public class SqliteFileOrganizationRepository : IFileOrganizationRepository, IDisposable { private IDbConnection _connection; diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs new file mode 100644 index 000000000..5f225ddd4 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs @@ -0,0 +1,338 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Server.Implementations.Persistence; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Security +{ + public class AuthenticationRepository : IAuthenticationRepository + { + 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 _saveInfoCommand; + + public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveInfoCommand = _connection.CreateCommand(); + _saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)"; + + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked"); + } + + public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) + { + info.Id = Guid.NewGuid().ToString("N"); + + return Update(info, cancellationToken); + } + + public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id); + _saveInfoCommand.GetParameter(index++).Value = info.AccessToken; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceId; + _saveInfoCommand.GetParameter(index++).Value = info.AppName; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceName; + _saveInfoCommand.GetParameter(index++).Value = info.UserId; + _saveInfoCommand.GetParameter(index++).Value = info.IsActive; + _saveInfoCommand.GetParameter(index++).Value = info.DateCreated; + _saveInfoCommand.GetParameter(index++).Value = info.DateRevoked; + + _saveInfoCommand.Transaction = transaction; + + _saveInfoCommand.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 const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText; + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (startIndex > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens ORDER BY DateCreated LIMIT {0})", + startIndex.ToString(_usCulture))); + } + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=@AccessToken"); + cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken; + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=@UserId"); + cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId; + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=@DeviceId"); + cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId; + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=@IsActive"); + cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value; + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from AccessTokens"; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(Get(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText + " 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 Get(reader); + } + } + } + + return null; + } + + private AuthenticationInfo Get(IDataReader reader) + { + var s = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + var info = new AuthenticationInfo + { + Id = reader.GetGuid(0).ToString("N"), + AccessToken = reader.GetString(1) + }; + + if (!reader.IsDBNull(2)) + { + info.DeviceId = reader.GetString(2); + } + + if (!reader.IsDBNull(3)) + { + info.AppName = reader.GetString(3); + } + + if (!reader.IsDBNull(4)) + { + info.DeviceName = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + info.UserId = reader.GetString(5); + } + + info.IsActive = reader.GetBoolean(6); + info.DateCreated = reader.GetDateTime(7); + + if (!reader.IsDBNull(8)) + { + info.DateRevoked = reader.GetDateTime(8); + } + + return info; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + try + { + lock (_disposeLock) + { + if (_connection != null) + { + if (_connection.IsOpen()) + { + _connection.Close(); + } + + _connection.Dispose(); + _connection = null; + } + } + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing database", ex); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 784719318..c3d24c0de 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,6 +1,4 @@ -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Common.Events; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -13,12 +11,14 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Users; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,7 +27,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Server.Implementations.Session { @@ -62,6 +61,8 @@ namespace MediaBrowser.Server.Implementations.Session private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; + private readonly IAuthenticationRepository _authRepo; + /// /// Gets or sets the configuration manager. /// @@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session /// The logger. /// The user repository. /// The library manager. - public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient) + public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo) { _userDataRepository = userDataRepository; _configurationManager = configurationManager; @@ -119,6 +120,7 @@ namespace MediaBrowser.Server.Implementations.Session _jsonSerializer = jsonSerializer; _appHost = appHost; _httpClient = httpClient; + _authRepo = authRepo; } /// @@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session /// Task. /// user /// - public async Task LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + public async Task LogSessionActivity(string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { if (string.IsNullOrEmpty(clientType)) { @@ -1157,7 +1164,37 @@ namespace MediaBrowser.Server.Implementations.Session public void ValidateSecurityToken(string token) { + if (string.IsNullOrWhiteSpace(token)) + { + throw new UnauthorizedAccessException(); + } + var result = _authRepo.Get(new AuthenticationInfoQuery + { + AccessToken = token + }); + + var info = result.Items.FirstOrDefault(); + + if (info == null) + { + throw new UnauthorizedAccessException(); + } + + if (!info.IsActive) + { + throw new UnauthorizedAccessException("Access token has expired."); + } + + if (!string.IsNullOrWhiteSpace(info.UserId)) + { + var user = _userManager.GetUserById(new Guid(info.UserId)); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); + } + } } /// @@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session /// public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) { - var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { @@ -1185,6 +1222,8 @@ namespace MediaBrowser.Server.Implementations.Session var user = _userManager.Users .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false); + var session = await LogSessionActivity(clientType, appVersion, deviceId, @@ -1197,11 +1236,108 @@ namespace MediaBrowser.Server.Implementations.Session { User = _dtoService.GetUserDto(user), SessionInfo = GetSessionInfoDto(session), - AuthenticationToken = Guid.NewGuid().ToString("N") + AccessToken = token }; } - private bool IsLocal(string remoteEndpoint) + private async Task GetAuthorizationToken(string userId, string deviceId, string app, string deviceName) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = deviceId, + IsActive = true, + UserId = userId, + Limit = 1 + }); + + if (existing.Items.Length > 0) + { + _logger.Debug("Reissuing access token"); + return existing.Items[0].AccessToken; + } + + var newToken = new AuthenticationInfo + { + AppName = app, + DateCreated = DateTime.UtcNow, + DeviceId = deviceId, + DeviceName = deviceName, + UserId = userId, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N") + }; + + _logger.Debug("Creating new access token for user {0}", userId); + await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false); + + return newToken.AccessToken; + } + + public async Task Logout(string accessToken) + { + if (string.IsNullOrWhiteSpace(accessToken)) + { + throw new ArgumentNullException("accessToken"); + } + + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + Limit = 1, + AccessToken = accessToken + + }).Items.FirstOrDefault(); + + if (existing != null) + { + existing.IsActive = false; + + await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try + { + ReportSessionEnded(session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended", ex); + } + } + } + } + + public async Task RevokeUserTokens(string userId) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true, + UserId = userId + }); + + foreach (var info in existing.Items) + { + await Logout(info.AccessToken).ConfigureAwait(false); + } + } + + private bool IsLocalhost(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + } + + public bool IsLocal(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { @@ -1211,12 +1347,11 @@ namespace MediaBrowser.Server.Implementations.Session // Private address space: // http://en.wikipedia.org/wiki/Private_network - return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + return IsLocalhost(remoteEndpoint) || remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 91e92e21c..ca04e580f 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.ServerApplication private IUserViewManager UserViewManager { get; set; } + private IAuthenticationRepository AuthenticationRepository { get; set; } + /// /// Initializes a new instance of the class. /// @@ -586,6 +588,9 @@ namespace MediaBrowser.ServerApplication FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); RegisterSingleInstance(FileOrganizationRepository); + AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); + RegisterSingleInstance(AuthenticationRepository); + UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer); RegisterSingleInstance(UserManager); @@ -625,7 +630,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); RegisterSingleInstance(DtoService); - SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient); + SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository); RegisterSingleInstance(SessionManager); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); @@ -651,7 +656,7 @@ namespace MediaBrowser.ServerApplication var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient); RegisterSingleInstance(connectionManager); - var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); RegisterSingleInstance(collectionManager); LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); @@ -678,7 +683,7 @@ namespace MediaBrowser.ServerApplication var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext)); + RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext, ServerConfigurationManager)); RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); @@ -755,6 +760,15 @@ namespace MediaBrowser.ServerApplication return repo; } + private async Task GetAuthenticationRepository() + { + var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); + + await repo.Initialize().ConfigureAwait(false); + + return repo; + } + /// /// Configures the repositories. /// diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 0515148f0..49851f42a 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The cancellation token. private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(metadataFile, encoding)) + using (var streamReader = new StreamReader(metadataFile)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs index 65648e1d0..47371ea04 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Providers var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -44,10 +44,10 @@ namespace MediaBrowser.XbmcMetadata.Providers { result.HasMetadata = false; } - finally - { - XmlProviderUtils.XmlParsingResourcePool.Release(); - } + //finally + //{ + // XmlProviderUtils.XmlParsingResourcePool.Release(); + //} return result; } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs index 1b6c7a340..a96b0636f 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs @@ -1,15 +1,14 @@ -using System.Collections.Generic; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security; using System.Text; using System.Threading; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; namespace MediaBrowser.XbmcMetadata.Savers { diff --git a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs index 7f817f591..7111a429a 100644 --- a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security; -using System.Text; -using System.Xml; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -18,6 +10,14 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.XbmcMetadata.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; namespace MediaBrowser.XbmcMetadata.Savers { @@ -392,9 +392,9 @@ namespace MediaBrowser.XbmcMetadata.Savers builder.Append("" + SecurityElement.Escape(person) + ""); } - if (writers.Count > 0) + foreach (var person in writers) { - builder.Append("" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + ""); + builder.Append("" + SecurityElement.Escape(person) + ""); } var hasTrailer = item as IHasTrailers; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 8c83ffabc..f9e8c5aad 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.412 + 3.0.414 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 88431b72c..89aeff0a3 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.412 + 3.0.414 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 73f764fd5..1696ee347 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.412 + 3.0.414 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + -- cgit v1.2.3 From 651d483dec489a84bf93fe900e537cc7be9c4cbd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 8 Jul 2014 20:46:11 -0400 Subject: rework nfo savers --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- .../Channels/ChannelManager.cs | 6 +- .../HttpServer/HttpListenerHost.cs | 226 ++--- .../HttpServer/HttpResultFactory.cs | 7 +- .../HttpServer/LoggerUtils.cs | 15 +- .../HttpServer/Security/AuthService.cs | 6 +- .../Localization/JavaScript/es_ES.json | 219 +++++ .../Localization/JavaScript/ru.json | 2 +- .../Localization/LocalizationManager.cs | 2 +- .../Localization/Server/es_ES.json | 850 ++++++++++++++++++ .../Localization/Server/es_MX.json | 8 +- .../Localization/Server/it.json | 12 +- .../Localization/Server/pl.json | 14 +- .../Localization/Server/pt_BR.json | 10 +- .../Localization/Server/ru.json | 24 +- .../Localization/Server/sv.json | 16 +- .../MediaBrowser.Server.Implementations.csproj | 2 + .../FFMpeg/FFMpegDownloadInfo.cs | 19 +- MediaBrowser.Tests/MediaBrowser.Tests.csproj | 4 + .../MediaBrowser.XbmcMetadata.csproj | 14 +- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 9 +- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 112 +++ MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs | 134 --- MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs | 94 ++ MediaBrowser.XbmcMetadata/Savers/ArtistXmlSaver.cs | 116 --- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 997 +++++++++++++++++++++ .../Savers/EpisodeNfoSaver.cs | 122 +++ .../Savers/EpisodeXmlSaver.cs | 141 --- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 113 +++ MediaBrowser.XbmcMetadata/Savers/MovieXmlSaver.cs | 137 --- MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs | 63 ++ MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs | 86 -- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 113 +++ MediaBrowser.XbmcMetadata/Savers/SeriesXmlSaver.cs | 122 --- .../Savers/XmlSaverHelpers.cs | 910 ------------------- 35 files changed, 2897 insertions(+), 1830 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json create mode 100644 MediaBrowser.Server.Implementations/Localization/Server/es_ES.json create mode 100644 MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/ArtistXmlSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/EpisodeXmlSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/MovieXmlSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs create mode 100644 MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/SeriesXmlSaver.cs delete mode 100644 MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index d8e3ee75d..094a9034d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -953,7 +953,7 @@ namespace MediaBrowser.Api.Playback // This is arbitrary, but add a little buffer time when internet streaming if (state.InputProtocol != MediaProtocol.File) { - await Task.Delay(3000, cancellationTokenSource.Token).ConfigureAwait(false); + await Task.Delay(2500, cancellationTokenSource.Token).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index d0ea64e0f..91bb1cbe6 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -16,6 +15,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -1167,6 +1167,8 @@ namespace MediaBrowser.Server.Implementations.Channels var mediaSource = info.MediaSources.FirstOrDefault(); item.Path = mediaSource == null ? null : mediaSource.Path; + + item.DisplayMediaType = channelMediaItem.ContentType.ToString(); } if (isNew) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index cfcbb077e..340149182 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,13 +1,12 @@ -using Funq; +using Amib.Threading; +using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.HttpServer.Security; using ServiceStack; using ServiceStack.Api.Swagger; -using ServiceStack.Auth; using ServiceStack.Host; using ServiceStack.Host.Handlers; using ServiceStack.Host.HttpListener; @@ -38,8 +37,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer private HttpListener Listener { get; set; } protected bool IsStarted = false; - private readonly List _autoResetEvents = new List(); + private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); + private readonly SmartThreadPool _threadPoolManager; + private const int IdleTimeout = 300; + private readonly ContainerAdapter _containerAdapter; private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -64,10 +66,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer _containerAdapter = new ContainerAdapter(applicationHost); - for (var i = 0; i < 1; i++) - { - _autoResetEvents.Add(new AutoResetEvent(false)); - } + _threadPoolManager = new SmartThreadPool(IdleTimeout, + maxWorkerThreads: Math.Max(16, Environment.ProcessorCount * 2)); } public override void Configure(Container container) @@ -95,9 +95,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer Plugins.Add(new SwaggerFeature()); Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); - Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { - new SessionAuthProvider(_containerAdapter.Resolve()), - })); + //Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { + // new SessionAuthProvider(_containerAdapter.Resolve()), + //})); HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } @@ -115,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Config.HandlerFactoryPath = string.IsNullOrEmpty(HandlerPath) ? null - : "/" + HandlerPath; + : HandlerPath; Config.MetadataRedirectPath = string.IsNullOrEmpty(HandlerPath) ? "metadata" @@ -148,14 +148,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer public override ServiceStackHost Start(string listeningAtUrlBase) { - StartListener(); + StartListener(Listen); return this; } /// /// Starts the Web Service /// - private void StartListener() + private void StartListener(WaitCallback listenCallback) { // *** Already running - just leave it in place if (IsStarted) @@ -164,6 +164,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (Listener == null) Listener = new HttpListener(); + HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); + foreach (var prefix in UrlPrefixes) { _logger.Info("Adding HttpListener prefix " + prefix); @@ -175,11 +177,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Listener.Start(); _logger.Info("HttpListener started"); - for (var i = 0; i < _autoResetEvents.Count; i++) - { - var index = i; - ThreadPool.QueueUserWorkItem(o => Listen(o, index)); - } + ThreadPool.QueueUserWorkItem(listenCallback); } private bool IsListening @@ -188,7 +186,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } // Loop here to begin processing of new requests. - private void Listen(object state, int index) + private void Listen(object state) { while (IsListening) { @@ -196,9 +194,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer try { - Listener.BeginGetContext(c => ListenerCallback(c, index), Listener); - - _autoResetEvents[index].WaitOne(); + Listener.BeginGetContext(ListenerCallback, Listener); + _listenForNextRequest.WaitOne(); } catch (Exception ex) { @@ -210,19 +207,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer } // Handle the processing of a request in here. - private void ListenerCallback(IAsyncResult asyncResult, int index) + private void ListenerCallback(IAsyncResult asyncResult) { var listener = asyncResult.AsyncState as HttpListener; - HttpListenerContext context = null; + HttpListenerContext context; if (listener == null) return; + var isListening = listener.IsListening; try { - if (!IsListening) + if (!isListening) { - _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); - return; + _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return; } // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, // blocks until there is a request to be processed or some type of data is available. @@ -244,80 +241,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer // so that it calls the BeginGetContext() (or possibly exits if we're not // listening any more) method to start handling the next incoming request // while we continue to process this request on a different thread. - _autoResetEvents[index].Set(); + _listenForNextRequest.Set(); } - var date = DateTime.Now; + _threadPoolManager.QueueWorkItem(() => InitTask(context)); + } - Task.Factory.StartNew(async () => + public virtual void InitTask(HttpListenerContext context) + { + try { - try - { - var request = context.Request; - - LogHttpRequest(request, index); - - if (request.IsWebSocketRequest) - { - await ProcessWebSocketRequest(context).ConfigureAwait(false); - return; - } - - var localPath = request.Url.LocalPath; - - if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) - { - context.Response.Redirect(DefaultRedirectPath); - context.Response.Close(); - return; - } - if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) - { - context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); - return; - } - if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) - { - context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); - return; - } - if (string.IsNullOrEmpty(localPath)) - { - context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); - return; - } - - var url = request.Url.ToString(); - var endPoint = request.RemoteEndPoint; - - await ProcessRequestAsync(context).ConfigureAwait(false); + var task = this.ProcessRequestAsync(context); + task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); - var duration = DateTime.Now - date; - - if (EnableHttpRequestLogging) - { - LoggerUtils.LogResponse(_logger, context.Response, url, endPoint, duration); - } - } - catch (Exception ex) + if (task.Status == TaskStatus.Created) { - _logger.ErrorException("ProcessRequest failure", ex); - - HandleError(ex, context, _logger); + task.RunSynchronously(); } - - }); + } + catch (Exception ex) + { + HandleError(ex, context); + } } /// /// Logs the HTTP request. /// /// The request. - /// The index. - private void LogHttpRequest(HttpListenerRequest request, int index) + private void LogHttpRequest(HttpListenerRequest request) { var endpoint = request.LocalEndPoint; @@ -330,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (EnableHttpRequestLogging) { - LoggerUtils.LogRequest(_logger, request, index); + LoggerUtils.LogRequest(_logger, request); } } @@ -359,7 +311,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer #endif } - public static void HandleError(Exception ex, HttpListenerContext context, ILogger logger) + private void HandleError(Exception ex, HttpListenerContext context) { try { @@ -372,6 +324,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer return; } + var errorResponse = new ErrorResponse + { + ResponseStatus = new ResponseStatus + { + ErrorCode = ex.GetType().GetOperationName(), + Message = ex.Message, + StackTrace = ex.StackTrace, + } + }; + var contentType = httpReq.ResponseContentType; var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType); @@ -394,23 +356,13 @@ 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(); } catch (Exception errorEx) { - logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx); + _logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx); } } @@ -444,6 +396,44 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// protected Task ProcessRequestAsync(HttpListenerContext context) { + var request = context.Request; + + LogHttpRequest(request); + + if (request.IsWebSocketRequest) + { + return ProcessWebSocketRequest(context); + } + + var localPath = request.Url.LocalPath; + + if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) + { + context.Response.Redirect(DefaultRedirectPath); + context.Response.Close(); + return Task.FromResult(true); + } + if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) + { + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); + context.Response.Close(); + return Task.FromResult(true); + } + if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) + { + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); + context.Response.Close(); + return Task.FromResult(true); + } + if (string.IsNullOrEmpty(localPath)) + { + context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); + context.Response.Close(); + return Task.FromResult(true); + } + + var date = DateTime.Now; + if (string.IsNullOrEmpty(context.Request.RawUrl)) return ((object)null).AsTaskResult(); @@ -451,10 +441,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; - //var pathInfo = httpReq.PathInfo; - var handler = HttpHandlerFactory.GetHandler(httpReq); - //var handler = HttpHandlerFactory.GetHandlerForPathInfo(httpReq.HttpMethod, pathInfo, pathInfo, httpReq.GetPhysicalPath()); + + var url = request.Url.ToString(); + var remoteIp = httpReq.RemoteIp; var serviceStackHandler = handler as IServiceStackHandler; if (serviceStackHandler != null) @@ -466,7 +456,22 @@ namespace MediaBrowser.Server.Implementations.HttpServer } var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName); - task.ContinueWith(x => httpRes.Close()); + + task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); + //Matches Exceptions handled in HttpListenerBase.InitTask() + + var statusCode = httpRes.StatusCode; + + task.ContinueWith(x => + { + var duration = DateTime.Now - date; + + if (EnableHttpRequestLogging) + { + LoggerUtils.LogResponse(_logger, statusCode, url, remoteIp, duration); + } + + }, TaskContinuationOptions.None); return task; } @@ -496,6 +501,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer base.Init(); } + //public override RouteAttribute[] GetRouteAttributes(System.Type requestType) + //{ + // var routes = base.GetRouteAttributes(requestType); + // routes.Each(x => x.Path = "/api" + x.Path); + // return routes; + //} + /// /// Releases the specified instance. /// @@ -518,6 +530,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (disposing) { + _threadPoolManager.Dispose(); + Stop(); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 925ef8050..8831d635c 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -382,9 +382,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer return result; } + return GetNonCachedResult(requestContext, contentType, factoryFn, responseHeaders, isHeadRequest); + } + + private async Task GetNonCachedResult(IRequest requestContext, string contentType, Func> factoryFn, IDictionary responseHeaders = null, bool isHeadRequest = false) + { var compress = ShouldCompressResponse(requestContext, contentType); - var hasOptions = GetStaticResult(requestContext, responseHeaders, contentType, factoryFn, compress, isHeadRequest).Result; + var hasOptions = await GetStaticResult(requestContext, responseHeaders, contentType, factoryFn, compress, isHeadRequest).ConfigureAwait(false); AddResponseHeaders(hasOptions, responseHeaders); diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs index 3c8f86b1e..19d2f9c45 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,7 +1,5 @@ -using System.Globalization; -using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Logging; using System; -using System.Linq; using System.Net; using System.Text; @@ -14,8 +12,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// /// The logger. /// The request. - /// Index of the worker. - public static void LogRequest(ILogger logger, HttpListenerRequest request, int workerIndex) + public static void LogRequest(ILogger logger, HttpListenerRequest request) { var log = new StringBuilder(); @@ -32,21 +29,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// Logs the response. /// /// The logger. - /// The response. + /// The status code. /// The URL. /// The end point. /// The duration. - public static void LogResponse(ILogger logger, HttpListenerResponse response, string url, IPEndPoint endPoint, TimeSpan duration) + public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration) { - var statusCode = response.StatusCode; - var log = new StringBuilder(); log.AppendLine(string.Format("Url: {0}", url)); //log.AppendLine("Headers: " + string.Join(",", response.Headers.AllKeys.Select(k => k + "=" + response.Headers[k]))); - var responseTime = string.Format(". Response time: {0} ms. Content length: {1} bytes.", duration.TotalMilliseconds, response.ContentLength64.ToString(CultureInfo.InvariantCulture)); + var responseTime = string.Format(". Response time: {0} ms.", duration.TotalMilliseconds); var msg = "HTTP Response " + statusCode + " to " + endPoint + responseTime; diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 6894d7ac7..74ec325c6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -46,10 +46,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security if (HostContext.HasValidAuthSecret(req)) return; - ExecuteBasic(req, res, requestDto); //first check if session is authenticated - if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) + //ExecuteBasic(req, res, requestDto); //first check if session is authenticated + //if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) - ValidateUser(req); + //ValidateUser(req); } private void ValidateUser(IRequest req) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json new file mode 100644 index 000000000..1073d16cf --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json @@ -0,0 +1,219 @@ +{ + "SettingsSaved": "Configuraci\u00f3n guardada", + "AddUser": "Agregar usuario", + "Users": "Usuarios", + "Delete": "Borrar", + "Administrator": "Administrador", + "Password": "Contrase\u00f1a", + "DeleteImage": "Borrar Imagen", + "DeleteImageConfirmation": "Est\u00e1 seguro que desea borrar esta imagen?", + "FileReadCancelled": "La lectura del archivo se ha cancelado.", + "FileNotFound": "Archivo no encontrado.", + "FileReadError": "Se encontr\u00f3 un error al leer el archivo.", + "DeleteUser": "Borrar Usuario", + "DeleteUserConfirmation": "Esta seguro que desea eliminar a {0}?", + "PasswordResetHeader": "Restablecer contrase\u00f1a", + "PasswordResetComplete": "La contrase\u00f1a se ha restablecido.", + "PasswordResetConfirmation": "Esta seguro que desea restablecer la contrase\u00f1a?", + "PasswordSaved": "Contrase\u00f1a guardada.", + "PasswordMatchError": "La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.", + "OptionRelease": "Release Oficial", + "OptionBeta": "Beta", + "OptionDev": "Desarrollo", + "UninstallPluginHeader": "Desinstalar Plugin", + "UninstallPluginConfirmation": "Esta seguro que desea desinstalar {0}?", + "NoPluginConfigurationMessage": "El plugin no requiere configuraci\u00f3n", + "NoPluginsInstalledMessage": "No tiene plugins instalados.", + "BrowsePluginCatalogMessage": "Navegar el catalogo de plugins para ver los plugins disponibles.", + "MessageKeyEmailedTo": "Clave enviada por email a {0}.", + "MessageKeysLinked": "Claves vinculadas.", + "HeaderConfirmation": "Confirmaci\u00f3n", + "MessageKeyUpdated": "Gracias. Su clave de seguidor ha sido actualizada.", + "MessageKeyRemoved": "Gracias. Su clave de seguidor ha sido eliminada.", + "ErrorLaunchingChromecast": "Ha habido un error al lanzar chromecast. Asegurese que su dispositivo est\u00e1 conectado a su red inal\u00e1mbrica.", + "HeaderSearch": "Buscar", + "LabelArtist": "Artista", + "LabelMovie": "Pel\u00edcula", + "LabelMusicVideo": "Video Musical", + "LabelEpisode": "Episodio", + "LabelSeries": "Series", + "LabelStopping": "Deteniendo", + "ButtonStop": "Detener", + "LabelCancelled": "(cancelado)", + "LabelFailed": "(fracasado)", + "LabelAbortedByServerShutdown": "(Abortado por cierre del servidor)", + "LabelScheduledTaskLastRan": "\u00daltima ejecuci\u00f3n {0}, teniendo {1}.", + "HeaderDeleteTaskTrigger": "Eliminar tarea de activaci\u00f3n", + "HeaderTaskTriggers": "Tareas de activaci\u00f3n", + "MessageDeleteTaskTrigger": "\u00bfEst\u00e1 seguro que desea eliminar esta tarea de activaci\u00f3n?", + "MessageNoPluginsInstalled": "No tiene plugins instalados.", + "LabelVersionInstalled": "{0} instalado", + "LabelNumberReviews": "{0} Revisiones", + "LabelFree": "Libre", + "HeaderSelectAudio": "Seleccionar Audio", + "HeaderSelectSubtitles": "Seleccionar Subt\u00edtulos", + "LabelDefaultStream": "(Por defecto)", + "LabelForcedStream": "(Forzado)", + "LabelDefaultForcedStream": "(Por defecto\/Forzado)", + "LabelUnknownLanguage": "Idioma desconocido", + "ButtonMute": "Silencio", + "ButtonUnmute": "Activar audio", + "ButtonNextTrack": "Next Track", + "ButtonPause": "Pausa", + "ButtonPlay": "Reproducir", + "ButtonEdit": "Editar", + "ButtonQueue": "En cola", + "ButtonPlayTrailer": "Reproducir trailer", + "ButtonPlaylist": "Lista de reproducci\u00f3n", + "ButtonPreviousTrack": "Previous Track", + "LabelEnabled": "Activado", + "LabelDisabled": "Desactivado", + "ButtonMoreInformation": "M\u00e1s informaci\u00f3n", + "LabelNoUnreadNotifications": "No hay notificaciones sin leer.", + "ButtonViewNotifications": "Ver notificaciones", + "ButtonMarkTheseRead": "Marcar como le\u00eddo", + "ButtonClose": "Cerrar", + "LabelAllPlaysSentToPlayer": "Todas las reproducciones se enviar\u00e1n al reproductor seleccionado.", + "MessageInvalidUser": "Usuario o contrase\u00f1a no v\u00e1lido.", + "HeaderAllRecordings": "Todas la grabaciones", + "RecommendationBecauseYouLike": "Como le gusta {0}", + "RecommendationBecauseYouWatched": "Ya que vi\u00f3 {0}", + "RecommendationDirectedBy": "Dirigida por {0}", + "RecommendationStarring": "Protagonizada por {0}", + "HeaderConfirmRecordingCancellation": "Confirmar la cancelaci\u00f3n de la grabaci\u00f3n", + "MessageConfirmRecordingCancellation": "\u00bfEst\u00e1 seguro que desea cancelar esta grabaci\u00f3n?", + "MessageRecordingCancelled": "Grabaci\u00f3n cancelada.", + "HeaderConfirmSeriesCancellation": "Confirmar cancelaci\u00f3n de serie", + "MessageConfirmSeriesCancellation": "\u00bfEst\u00e1 seguro que desea cancelar esta serie?", + "MessageSeriesCancelled": "Serie cancelada", + "HeaderConfirmRecordingDeletion": "Confirmar borrado de la grabaci\u00f3n", + "MessageConfirmRecordingDeletion": "\u00bfEst\u00e1 seguro que desea borrar esta grabaci\u00f3n?", + "MessageRecordingDeleted": "Grabaci\u00f3n eliminada.", + "ButonCancelRecording": "Cancelar Grabaci\u00f3n", + "MessageRecordingSaved": "Grabaci\u00f3n guardada.", + "OptionSunday": "Domingo", + "OptionMonday": "Lunes", + "OptionTuesday": "Martes", + "OptionWednesday": "Mi\u00e9rcoles", + "OptionThursday": "Jueves", + "OptionFriday": "Viernes", + "OptionSaturday": "S\u00e1bado", + "HeaderConfirmDeletion": "Confirmar borrado", + "MessageConfirmPathSubstitutionDeletion": "\u00bfEst\u00e1 seguro que desea borrar esta ruta de sustituci\u00f3n?", + "LiveTvUpdateAvailable": "(Actualizaci\u00f3n disponible)", + "LabelVersionUpToDate": "\u00a1Actualizado!", + "ButtonResetTuner": "Reiniciar sintonizador", + "HeaderResetTuner": "Reinicio del sintonizador", + "MessageConfirmResetTuner": "\u00bfEst\u00e1 seguro que desea reiniciar este sintonizador? Cualquier reproducci\u00f3n o grabaci\u00f3n activa se detendr\u00e1 inmediatamente.", + "ButtonCancelSeries": "Cancelar serie", + "LabelAllChannels": "Todos los canales", + "HeaderSeriesRecordings": "Grabaciones de series", + "LabelAnytime": "A cualquier hora", + "StatusRecording": "Grabando", + "StatusWatching": "Viendo", + "StatusRecordingProgram": "Grabando {0}", + "StatusWatchingProgram": "Viendo {0}", + "HeaderSplitMedia": "Divisi\u00f3n de medios", + "MessageConfirmSplitMedia": "\u00bfEst\u00e1 seguro que desea dividir los medios en partes separadas?", + "HeaderError": "Error", + "MessagePleaseSelectOneItem": "Seleccione al menos un elemento.", + "MessagePleaseSelectTwoItems": "Seleccione al menos dos elementos.", + "MessageTheFollowingItemsWillBeGrouped": "Los siguientes t\u00edtulos se agrupar\u00e1n en un elemento.", + "MessageConfirmItemGrouping": "Los clientes de Media Browser elegir\u00e1n autom\u00e1ticamente la mejor forma de reproduccion sobre la base de dispositivo y rendimiento de la red. \u00bfEst\u00e1 seguro que desea continuar?", + "HeaderResume": "Continuar", + "HeaderMyViews": "Mis vistas", + "HeaderLibraryFolders": "Vista de carpeta", + "HeaderLatestMedia": "\u00daltimos medios", + "ButtonMore": "M\u00e1s...", + "HeaderFavoriteMovies": "Pel\u00edculas favoritas", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteGames": "Juegos favoritos", + "HeaderRatingsDownloads": "\nClasificaci\u00f3n \/ Descargas", + "HeaderConfirmProfileDeletion": "Confirmar borrado del perfil", + "MessageConfirmProfileDeletion": "\u00bfEst\u00e1 seguro que desea eliminar este perfil?", + "HeaderSelectServerCachePath": "Seleccione la ruta para el cach\u00e9 del servidor", + "HeaderSelectTranscodingPath": "Seleccione la ruta temporal del transcodificador", + "HeaderSelectImagesByNamePath": "Seleccione la ruta para im\u00e1genes", + "HeaderSelectMetadataPath": "Seleccione la ruta para Metadatos", + "HeaderSelectServerCachePathHelp": "Busque o escriba la ruta de acceso que se utilizar\u00e1 para los archivos de cach\u00e9 del servidor. La carpeta debe tener permiso de escritura. La ubicaci\u00f3n de esta carpeta afectar\u00e1 directamente al rendimiento del servidor e idealmente debe ser colocado en una unidad de estado s\u00f3lido.", + "HeaderSelectTranscodingPathHelp": "Busque o escriba la ruta de acceso que se utilizar\u00e1 para la transcodificaci\u00f3n de archivos temporales. La carpeta debe tener permiso de escritura.", + "HeaderSelectImagesByNamePathHelp": "Busque o escriba la ruta de sus elementos por nombre de carpeta. La carpeta debe tener permisos de escritura.", + "HeaderSelectMetadataPathHelp": "Busque o escriba la ruta donde desea almacenar los metadatos. La carpeta debe tener permiso de escritura.", + "HeaderSelectChannelDownloadPath": "Seleccione la ruta de descargas de canal", + "HeaderSelectChannelDownloadPathHelp": "Navege o escriba la ruta para guardar el los archivos de cach\u00e9 de canales. La carpeta debe tener permisos de escritura.", + "OptionNewCollection": "Nuevo...", + "ButtonAdd": "A\u00f1adir", + "ButtonRemove": "Quitar", + "LabelChapterDownloaders": "Downloaders de cap\u00edtulos:", + "LabelChapterDownloadersHelp": "Habilitar y clasificar sus descargadores de cap\u00edtulos preferidos en orden de prioridad. Descargadores de menor prioridad s\u00f3lo se utilizar\u00e1n para completar la informaci\u00f3n que falta.", + "HeaderFavoriteAlbums": "\u00c1lbumes favoritos", + "HeaderLatestChannelMedia": "\u00dcltimos elementos de canal", + "ButtonOrganizeFile": "Organizar archivos", + "ButtonDeleteFile": "Borrar archivos", + "HeaderOrganizeFile": "Organizar archivos", + "HeaderDeleteFile": "Borrar archivos", + "StatusSkipped": "Saltado", + "StatusFailed": "Err\u00f3neo", + "StatusSuccess": "\u00c9xito", + "MessageFileWillBeDeleted": "El siguiente archivo se eliminar\u00e1:", + "MessageSureYouWishToProceed": "\u00bfEst\u00e1 seguro que desea proceder?", + "MessageDuplicatesWillBeDeleted": "Adem\u00e1s se eliminar\u00e1n los siguientes duplicados:", + "MessageFollowingFileWillBeMovedFrom": "El siguiente archivo se mover\u00e1 desde:", + "MessageDestinationTo": "hasta:", + "HeaderSelectWatchFolder": "Seleccionar carpeta para el reloj", + "HeaderSelectWatchFolderHelp": "Navegue o introduzca la ruta para la carpeta para el reloj. La carpeta debe tener permisos de escritura.", + "OrganizePatternResult": "Resultado: {0}", + "HeaderRestart": "Reiniciar", + "HeaderShutdown": "Apagar", + "MessageConfirmRestart": "\u00bfEst\u00e1 seguro que desea reiniciar Media Browser Server?", + "MessageConfirmShutdown": "\u00bfEst\u00e1 seguro que desea apagar Media Browser Server?", + "ButtonUpdateNow": "Actualizar ahora", + "NewVersionOfSomethingAvailable": "\u00a1Hay disponible una nueva versi\u00f3n de {0}!", + "VersionXIsAvailableForDownload": "La versi\u00f3n {0} est\u00e1 disponible para su descarga.", + "LabelVersionNumber": "Versi\u00f3n {0}", + "LabelPlayMethodTranscoding": "Transcodificaci\u00f3n", + "LabelPlayMethodDirectStream": "Streaming directo", + "LabelPlayMethodDirectPlay": "Reproducci\u00f3n directa", + "LabelAudioCodec": "Audio: {0}", + "LabelVideoCodec": "Video: {0}", + "LabelRemoteAccessUrl": "Acceso remoto: {0}", + "LabelRunningOnPort": "Ejecut\u00e1ndose en el puerto {0}.", + "LabelRunningOnPorts": "Ejecut\u00e1ndose en los puertos {0} y {1}.", + "HeaderLatestFromChannel": "Lo \u00faltimo de {0}", + "ButtonDownload": "Descargar", + "LabelUnknownLanaguage": "Idioma desconocido", + "HeaderCurrentSubtitles": "Subt\u00edtulos actuales", + "MessageDownloadQueued": "La descarga se ha a\u00f1adido a la cola", + "MessageAreYouSureDeleteSubtitles": "\u00bfEst\u00e1 seguro que desea eliminar este archivo de subt\u00edtulos?", + "ButtonRemoteControl": "Control remoto", + "HeaderLatestTvRecordings": "\u00daltimas grabaciones", + "ButtonOk": "OK", + "ButtonCancel": "Cancelar", + "ButtonRefresh": "Refrescar", + "LabelCurrentPath": "Ruta actual:", + "HeaderSelectMediaPath": "Seleccionar la ruta para Medios", + "ButtonNetwork": "Red", + "MessageDirectoryPickerInstruction": "Rutas de red pueden ser introducidas manualmente en el caso de que el bot\u00f3n de la red no pueda localizar sus dispositivos. Por ejemplo, {0} o {1}.", + "HeaderMenu": "Men\u00fa", + "ButtonOpen": "Abrir", + "ButtonOpenInNewTab": "Abrir en nueva pesta\u00f1a", + "ButtonShuffle": "Mezclar", + "ButtonInstantMix": "Mix instant\u00e1neo", + "ButtonResume": "Continuar", + "HeaderScenes": "Escenas", + "HeaderAudioTracks": "Pistas de audio", + "HeaderSubtitles": "Subt\u00edtulos", + "HeaderVideoQuality": "Calidad de video", + "MessageErrorPlayingVideo": "Ha habido un error reproduciendo el video.", + "MessageEnsureOpenTuner": "Aseg\u00farese que hay un sintonizador disponible.", + "ButtonHome": "Inicio", + "ButtonDashboard": "Panel de control", + "ButtonReports": "Informes", + "ButtonMetadataManager": "Metadata Manager", + "HeaderTime": "Duraci\u00f3n", + "HeaderName": "Nombre", + "HeaderAlbum": "Album", + "HeaderAlbumArtist": "Artista del album", + "HeaderArtist": "Artista" +} \ 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 84a2efa83..0d6bc9e83 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -197,7 +197,7 @@ "MessageDirectoryPickerInstruction": "\u0421\u0435\u0442\u0435\u0432\u044b\u0435 \u043f\u0443\u0442\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u0421\u0435\u0442\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0439 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, {0} \u0438\u043b\u0438 {1}.", "HeaderMenu": "\u041c\u0435\u043d\u044e", "ButtonOpen": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c", - "ButtonOpenInNewTab": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0432 \u043d\u043e\u0432\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435", + "ButtonOpenInNewTab": "\u0412 \u043d\u043e\u0432\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435", "ButtonShuffle": "\u041f\u0435\u0440\u0435\u043c\u0435\u0448\u0430\u0442\u044c", "ButtonInstantMix": "\u0410\u0432\u0442\u043e\u043c\u0438\u043a\u0441", "ButtonResume": "\u0412\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c", diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs index c21c9fa17..05de4d65a 100644 --- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs +++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs @@ -375,7 +375,7 @@ namespace MediaBrowser.Server.Implementations.Localization new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"}, new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"}, new LocalizatonOption{ Name="Russian", Value="ru"}, - new LocalizatonOption{ Name="Spanish", Value="es"}, + new LocalizatonOption{ Name="Spanish", Value="es-ES"}, new LocalizatonOption{ Name="Spanish (Mexico)", Value="es-MX"}, new LocalizatonOption{ Name="Swedish", Value="sv"}, new LocalizatonOption{ Name="Vietnamese", Value="vi"} diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json new file mode 100644 index 000000000..fbd39b960 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json @@ -0,0 +1,850 @@ +{ + "LabelExit": "Salir", + "LabelVisitCommunity": "Visitar la comunidad", + "LabelGithubWiki": "Wiki de Github", + "LabelSwagger": "Swagger", + "LabelStandard": "Est\u00e1ndar", + "LabelViewApiDocumentation": "Ver documentacion de Api", + "LabelBrowseLibrary": "Navegar biblioteca", + "LabelConfigureMediaBrowser": "Configurar Media Browser", + "LabelOpenLibraryViewer": "Abrir el visor de la biblioteca", + "LabelRestartServer": "Reiniciar el servidor", + "LabelShowLogWindow": "Mostrar la ventana del log", + "LabelPrevious": "Anterior", + "LabelFinish": "Terminar", + "LabelNext": "Siguiente", + "LabelYoureDone": "Ha Terminado!", + "WelcomeToMediaBrowser": "\u00a1Bienvenido a Media Browser!", + "TitleMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "Este asistente lo guiar\u00e1 por el proceso de instalaci\u00f3n. Para comenzar seleccione su idioma preferido.", + "TellUsAboutYourself": "D\u00edganos acerca de usted", + "LabelYourFirstName": "Su nombre:", + "MoreUsersCanBeAddedLater": "M\u00e1s usuarios pueden agregarse m\u00e1s tarde en el panel de control.", + "UserProfilesIntro": "Media Browser incluye soporte integrado para los perfiles de usuario, lo que permite que cada usuario tenga su propia configuraci\u00f3n de la pantalla, estado de reproducci\u00f3n y control parental.", + "LabelWindowsService": "Servicio de Windows", + "AWindowsServiceHasBeenInstalled": "Un servicio de Windows se ha instalado", + "WindowsServiceIntro1": "Media Browser Server se ejecuta normalmente como una aplicaci\u00f3n de escritorio con un icono de la bandeja, pero si prefiere ejecutarlo como un servicio en segundo plano, se puede iniciar desde el panel de control de servicios de Windows en su lugar.", + "WindowsServiceIntro2": "Si se utiliza el servicio de Windows, tenga en cuenta que no se puede ejecutar al mismo tiempo que el icono de la bandeja, por lo que tendr\u00e1 que salir de la bandeja con el fin de ejecutar el servicio. Tambi\u00e9n tendr\u00e1 que ser configurado con privilegios administrativos a trav\u00e9s del panel de control del servicio. Tenga en cuenta que en este momento el servicio no es capaz de auto-actualizaci\u00f3n, por lo que las nuevas versiones requieren la interacci\u00f3n manual.", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Media Browser ha comenzado a reunir informaci\u00f3n sobre su biblioteca de medios. Echa un vistazo a algunas de nuestras aplicaciones, y luego haga clic en Finalizar<\/b> para ver el Panel de control<\/b>.", + "LabelConfigureSettings": "Configuraci\u00f3n de opciones", + "LabelEnableVideoImageExtraction": "Habilitar extracci\u00f3n de im\u00e1genes de video", + "VideoImageExtractionHelp": "Para los v\u00eddeos que no dispongan de im\u00e1genes y que no podemos encontrar en Internet. Esto agregar\u00e1 un tiempo adicional para la exploraci\u00f3n inicial de bibliotecas, pero resultar\u00e1 en una presentaci\u00f3n m\u00e1s agradable.", + "LabelEnableChapterImageExtractionForMovies": "Extraer im\u00e1genes de cap\u00edtulos para pel\u00edculas", + "LabelChapterImageExtractionForMoviesHelp": "Extraer im\u00e1genes de cap\u00edtulo permitir\u00e1 a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, intensivo en utilizaci\u00f3n del CPU y puede requerir varios gigabytes de espacio. Se ejecuta como una tarea nocturna, a las 4 de la ma\u00f1ana, aunque esto se puede configurar en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante las horas pico de uso.", + "LabelEnableAutomaticPortMapping": "Habilitar asignaci\u00f3n de puertos autom\u00e1tico", + "LabelEnableAutomaticPortMappingHelp": "UPnP permite la configuraci\u00f3n del router para acceso externo de forma f\u00e1cil y autom\u00e1tica. Esto puede no funcionar en algunos modelos de routers.", + "ButtonOk": "OK", + "ButtonCancel": "Cancelar", + "ButtonNew": "Nuevo", + "HeaderSetupLibrary": "Configurar biblioteca de medios", + "ButtonAddMediaFolder": "Agregar una carpeta de medios", + "LabelFolderType": "Tipo de carpeta:", + "MediaFolderHelpPluginRequired": "* Requiere el uso de un plugin, por ejemplo GameBrowser o MB Bookshelf", + "ReferToMediaLibraryWiki": "Consultar el wiki de la biblioteca de medios", + "LabelCountry": "Pa\u00eds:", + "LabelLanguage": "Idioma:", + "HeaderPreferredMetadataLanguage": "Idioma preferido para metadata", + "LabelSaveLocalMetadata": "Guardar im\u00e1genes y metadata en las carpetas de medios", + "LabelSaveLocalMetadataHelp": "Guardar im\u00e1genes y metadata directamente en las carpetas de medios, permitir\u00e1 colocarlas en un lugar donde se pueden editar f\u00e1cilmente.", + "LabelDownloadInternetMetadata": "Descargar imagenes y metadata de internet", + "LabelDownloadInternetMetadataHelp": "Media Browser permite descargar informaci\u00f3n acerca de su media para enriquecer la presentaci\u00f3n.", + "TabPreferences": "Preferencias", + "TabPassword": "Contrase\u00f1a", + "TabLibraryAccess": "Acceso a biblioteca", + "TabImage": "imagen", + "TabProfile": "Perfil", + "TabMetadata": "Metadata", + "TabImages": "Im\u00e1genes", + "TabNotifications": "Notificaciones", + "TabCollectionTitles": "T\u00edtulos", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostar episodios no disponibles en temporadas", + "LabelUnairedMissingEpisodesWithinSeasons": "Mostrar episodios a\u00fan no emitidos en temporadas", + "HeaderVideoPlaybackSettings": "Ajustes de Reproducci\u00f3n de Video", + "HeaderPlaybackSettings": "Ajustes de reproducci\u00f3n", + "LabelAudioLanguagePreference": "Preferencia de idioma de audio", + "LabelSubtitleLanguagePreference": "Preferencia de idioma de subtitulos", + "OptionDefaultSubtitles": "Por defecto", + "OptionOnlyForcedSubtitles": "S\u00f3lo subt\u00edtulos forzados", + "OptionAlwaysPlaySubtitles": "Mostrar siempre subt\u00edtulos", + "OptionNoSubtitles": "Sin subt\u00edtulos", + "OptionDefaultSubtitlesHelp": "Los subt\u00edtulos que concuerden con la preferencia de idioma se cargar\u00e1n cuando el audio est\u00e9 en un idioma extranjero.", + "OptionOnlyForcedSubtitlesHelp": "S\u00f3lo se cargar\u00e1n los subt\u00edtulos marcados como forzados.", + "OptionAlwaysPlaySubtitlesHelp": "Los subt\u00edtulos que concuerden con la preferencia de idioma se cargar\u00e1n independientemente del idioma de audio.", + "OptionNoSubtitlesHelp": "Los subt\u00edtulos no se cargar\u00e1n de forma predeterminada.", + "TabProfiles": "Perfiles", + "TabSecurity": "Seguridad", + "ButtonAddUser": "Agregar Usuario", + "ButtonSave": "Grabar", + "ButtonResetPassword": "Reiniciar Contrase\u00f1a", + "LabelNewPassword": "Nueva Contrase\u00f1a:", + "LabelNewPasswordConfirm": "Confirmaci\u00f3n de contrase\u00f1a nueva:", + "HeaderCreatePassword": "Crear Contrase\u00f1a", + "LabelCurrentPassword": "Contrase\u00f1a actual", + "LabelMaxParentalRating": "M\u00e1xima clasificaci\u00f3n permitida", + "MaxParentalRatingHelp": "El contenido con clasificaci\u00f3n parental superior se ocultar\u00e1 para este usuario.", + "LibraryAccessHelp": "Seleccione las carpetas de medios para compartir con este usuario. Los administradores podr\u00e1n editar todas las carpetas usando el gestor de metadata.", + "ChannelAccessHelp": "Seleccione los canales para compartir con este usuario. Los administradores podr\u00e1n editar todos los canales mediante el gestor de metadatos.", + "ButtonDeleteImage": "Borrar imagen", + "LabelSelectUsers": "Seleccionar usuarios:", + "ButtonUpload": "Subir", + "HeaderUploadNewImage": "Subir nueva imagen", + "LabelDropImageHere": "Depositar Imagen Aqu\u00ed", + "ImageUploadAspectRatioHelp": "Se Recomienda una Proporci\u00f3n de Aspecto 1:1. Solo JPG\/PNG", + "MessageNothingHere": "Nada aqu\u00ed.", + "MessagePleaseEnsureInternetMetadata": "Por favor aseg\u00farese que la descarga de metadata de internet esta habilitada", + "TabSuggested": "Sugerencia", + "TabLatest": "Novedades", + "TabUpcoming": "Pr\u00f3ximos", + "TabShows": "Programas", + "TabEpisodes": "Episodios", + "TabGenres": "G\u00e9neros", + "TabPeople": "Gente", + "TabNetworks": "redes", + "HeaderUsers": "Usuarios", + "HeaderFilters": "Filtros:", + "ButtonFilter": "Filtro", + "OptionFavorite": "Favoritos", + "OptionLikes": "Me gusta", + "OptionDislikes": "No me gusta", + "OptionActors": "Actores", + "OptionGuestStars": "Estrellas invitadas", + "OptionDirectors": "Directores", + "OptionWriters": "Guionistas", + "OptionProducers": "Productores", + "HeaderResume": "Continuar", + "HeaderNextUp": "Siguiendo", + "NoNextUpItemsMessage": "Nada encontrado. \u00a1Comienza a ver tus programas!", + "HeaderLatestEpisodes": "Ultimos episodios", + "HeaderPersonTypes": "Tipos de personas:", + "TabSongs": "Canciones", + "TabAlbums": "Albums", + "TabArtists": "Artistas", + "TabAlbumArtists": "Album Artistas", + "TabMusicVideos": "Videos Musicales", + "ButtonSort": "Ordenar", + "HeaderSortBy": "Ordenar por:", + "HeaderSortOrder": "Ordenado por:", + "OptionPlayed": "Reproducido", + "OptionUnplayed": "No reproducido", + "OptionAscending": "Ascendente", + "OptionDescending": "Descendente", + "OptionRuntime": "Tiempo", + "OptionReleaseDate": "Fecha de estreno", + "OptionPlayCount": "N\u00famero de reproducc.", + "OptionDatePlayed": "Fecha de reproducci\u00f3n", + "OptionDateAdded": "A\u00f1adido el", + "OptionAlbumArtist": "Album Artista", + "OptionArtist": "Artista", + "OptionAlbum": "Album", + "OptionTrackName": "Nombre de pista", + "OptionCommunityRating": "Valoraci\u00f3n comunidad", + "OptionNameSort": "Nombre", + "OptionFolderSort": "Carpetas", + "OptionBudget": "Presupuesto", + "OptionRevenue": "Recaudaci\u00f3n", + "OptionPoster": "Poster", + "OptionBackdrop": "Imagen de fondo", + "OptionTimeline": "L\u00ednea de tiempo", + "OptionThumb": "Miniatura", + "OptionBanner": "Banner", + "OptionCriticRating": "Valoraci\u00f3n cr\u00edtica", + "OptionVideoBitrate": "Video Bitrate", + "OptionResumable": "Se puede continuar", + "ScheduledTasksHelp": "Click en una tarea para ajustar su programaci\u00f3n", + "ScheduledTasksTitle": "Tareas programadas", + "TabMyPlugins": "Mis Plugins", + "TabCatalog": "Cat\u00e1logo", + "PluginsTitle": "Plugins", + "HeaderAutomaticUpdates": "Actualizaciones autom\u00e1ticas", + "HeaderNowPlaying": "Reproduciendo ahora", + "HeaderLatestAlbums": "\u00dcltimos Albums", + "HeaderLatestSongs": "\u00daltimas canciones", + "HeaderRecentlyPlayed": "Reproducido recientemente", + "HeaderFrequentlyPlayed": "Reproducido frequentemente", + "DevBuildWarning": "Las actualizaciones en desarrollo no est\u00e1n convenientemente probadas. La aplicaci\u00f3n se puede bloquear y caracter\u00edsticas completas pueden no funcionar del todo.", + "LabelVideoType": "Tipo de video", + "OptionBluray": "Bluray", + "OptionDvd": "Dvd", + "OptionIso": "Iso", + "Option3D": "3D", + "LabelFeatures": "Caracter\u00edsticas", + "LabelService": "Servicio:", + "LabelStatus": "Estado:", + "LabelVersion": "Versi\u00f3n:", + "LabelLastResult": "\u00daltimo resultado:", + "OptionHasSubtitles": "Subt\u00edtulos", + "OptionHasTrailer": "Trailer", + "OptionHasThemeSong": "Banda sonora", + "OptionHasThemeVideo": "Viideotema", + "TabMovies": "Pel\u00edculas", + "TabStudios": "Estudios", + "TabTrailers": "Trailers", + "HeaderLatestMovies": "\u00daltimas pel\u00edculas", + "HeaderLatestTrailers": "\u00daltimos trailers", + "OptionHasSpecialFeatures": "Caracter\u00edsticas especiales", + "OptionImdbRating": "Valoraci\u00f3n IMDb", + "OptionParentalRating": "Clasificaci\u00f3n parental", + "OptionPremiereDate": "Fecha de estreno", + "TabBasic": "B\u00e1sico", + "TabAdvanced": "Avanzado", + "HeaderStatus": "Estado", + "OptionContinuing": "Continuando", + "OptionEnded": "Finalizado", + "HeaderAirDays": "D\u00eda emisi\u00f3n", + "OptionSunday": "Domingo", + "OptionMonday": "Lunes", + "OptionTuesday": "Martes", + "OptionWednesday": "Mi\u00e9rcoles", + "OptionThursday": "Jueves", + "OptionFriday": "Viernes", + "OptionSaturday": "S\u00e1bado", + "HeaderManagement": "Administraci\u00f3n", + "OptionMissingImdbId": "Falta IMDb Id", + "OptionMissingTvdbId": "Falta TheTVDB Id", + "OptionMissingOverview": "Falta argumento", + "OptionFileMetadataYearMismatch": "Archivo\/Metadata a\u00f1os no coinciden", + "TabGeneral": "General", + "TitleSupport": "Soporte", + "TabLog": "Log", + "TabAbout": "Acerca de", + "TabSupporterKey": "Clave de Seguidor", + "TabBecomeSupporter": "Hazte Seguidor", + "MediaBrowserHasCommunity": "Media Browser cuenta con una pr\u00f3spera comunidad de usuarios y colaboradores.", + "CheckoutKnowledgeBase": "Echa un vistazo a nuestra base de conocimiento para ayudarte a sacar el m\u00e1ximo provecho de Media Browser.", + "SearchKnowledgeBase": "Buscar en la base de conocimiento", + "VisitTheCommunity": "Visitar la comunidad", + "VisitMediaBrowserWebsite": "Visitar la web de Media Browser", + "VisitMediaBrowserWebsiteLong": "Visita la web de Media Browser para estar informado de las \u00faltimas not\u00edcias y mantenerte al d\u00eda con el blog de desarrolladores.", + "OptionHideUser": "Ocultar este usuario en las pantallas de inicio de sesi\u00f3n", + "OptionDisableUser": "Deshabilitar este usuario", + "OptionDisableUserHelp": "Si est\u00e1 deshabilitado, el servidor no aceptar\u00e1 conexiones de este usuario. Si existen conexiones de este usuario, finalizar\u00e1n inmediatamente.", + "HeaderAdvancedControl": "Control avanzado", + "LabelName": "Nombre:", + "OptionAllowUserToManageServer": "Permite a este usuario administrar el servidor", + "HeaderFeatureAccess": "Permisos de acceso", + "OptionAllowMediaPlayback": "Permitir reproducci\u00f3n de medios", + "OptionAllowBrowsingLiveTv": "Acceso a TV en vivo", + "OptionAllowDeleteLibraryContent": "Permitir a este usuario eliminar contenido de la biblioteca", + "OptionAllowManageLiveTv": "Permitir la gesti\u00f3n de las grabaciones de TV en vivo", + "OptionAllowRemoteControlOthers": "Permitir a este usuario controlar rem\u00f3tamente a otros usuarios", + "OptionMissingTmdbId": "Falta Tmdb Id", + "OptionIsHD": "HD", + "OptionIsSD": "SD", + "OptionMetascore": "Metavalor", + "ButtonSelect": "Seleccionar", + "ButtonSearch": "Buscar", + "ButtonGroupVersions": "Versiones de Grupo", + "ButtonAddToCollection": "A\u00f1adir a la colecci\u00f3n", + "PismoMessage": "Usando Pismo File Mount a trav\u00e9s de una licencia donada.", + "TangibleSoftwareMessage": "Utilizamos convertidores Java\/C# de Tangible Solutions a trav\u00e9s de una licencia donada.", + "HeaderCredits": "Cr\u00e9ditos", + "PleaseSupportOtherProduces": "Por favor apoye otros productos gratuitos que utilizamos:", + "VersionNumber": "Versi\u00f3n {0}", + "TabPaths": "Ruta", + "TabServer": "Servidor", + "TabTranscoding": "Transcodificaci\u00f3n", + "TitleAdvanced": "Avanzado", + "LabelAutomaticUpdateLevel": "Actualizaci\u00f3n de nivel autom\u00e1tica", + "OptionRelease": "Release Oficial", + "OptionBeta": "Beta", + "OptionDev": "Desarrollo", + "LabelAllowServerAutoRestart": "Permitir al servidor reiniciarse autom\u00e1ticamente para aplicar las actualizaciones", + "LabelAllowServerAutoRestartHelp": "El servidor s\u00f3lo se reiniciar\u00e1 durante periodos de reposo, cuando no hayan usuarios activos.", + "LabelEnableDebugLogging": "Habilitar entrada de debug", + "LabelRunServerAtStartup": "Arrancar servidor al iniciar", + "LabelRunServerAtStartupHelp": "Esto iniciar\u00e1 como aplicaci\u00f3n en el inicio. Para iniciar en modo servicio de windows, desmarque esto e inicie el servicio desde el panel de control de windows. Tenga en cuenta que no es posible inciar de las dos formas a la vez, usted debe salir de la aplicaci\u00f3n para iniciar el servicio.", + "ButtonSelectDirectory": "Seleccionar directorio", + "LabelCustomPaths": "Especificar las rutas personalizadas que desee. D\u00e9jelo en blanco para usar las rutas por defecto.", + "LabelCachePath": "Ruta del cach\u00e9:", + "LabelCachePathHelp": "Esta carpeta contienes archivos de cach\u00e9 del servidor, tales como im\u00e1genes.", + "LabelImagesByNamePath": "Ruta de im\u00e1genes:", + "LabelImagesByNamePathHelp": "Esta carpeta contiene im\u00e1genes de actores, artistas, g\u00e9neros y estudios.", + "LabelMetadataPath": "Ruta de Metadata:", + "LabelMetadataPathHelp": "Esta localizaci\u00f3n contiene im\u00e1genes y metadata descargados que no est\u00e1n configurados para ser guardados en carpetas de medios.", + "LabelTranscodingTempPath": "Ruta temporal de transcodificaci\u00f3n:", + "LabelTranscodingTempPathHelp": "Esta carpeta contiene archivos de trabajo usados por el transcodificador.", + "TabBasics": "Basicos", + "TabTV": "TV", + "TabGames": "Juegos", + "TabMusic": "M\u00fasica", + "TabOthers": "Otros", + "HeaderExtractChapterImagesFor": "Extraer im\u00e1genes de cap\u00edtulos para:", + "OptionMovies": "Pel\u00edculas", + "OptionEpisodes": "Episodios", + "OptionOtherVideos": "Otros v\u00eddeos", + "TitleMetadata": "Metadata", + "LabelAutomaticUpdatesFanart": "Activar actualizaciones autom\u00e1ticas desde FanArt.tv", + "LabelAutomaticUpdatesTmdb": "Activar actualizaciones autom\u00e1ticas desde TheMovieDB.org", + "LabelAutomaticUpdatesTvdb": "Activar actualizaciones autom\u00e1ticas desde TheTVDB.com", + "LabelAutomaticUpdatesFanartHelp": "Si est\u00e1 activado, las nuevas im\u00e1genes se descargan autom\u00e1ticamente a medida que se a\u00f1aden a fanart.tv. Im\u00e1genes existentes no ser\u00e1n reemplazadas.", + "LabelAutomaticUpdatesTmdbHelp": "Si est\u00e1 activado, las nuevas im\u00e1genes se descargan autom\u00e1ticamente a medida que se a\u00f1aden a TheMovieDB.org. Im\u00e1genes existentes no ser\u00e1n reemplazados.", + "LabelAutomaticUpdatesTvdbHelp": "Si est\u00e1 activado, las nuevas im\u00e1genes se descargan autom\u00e1ticamente a medida que se a\u00f1aden a TheTVDB.com. Im\u00e1genes existentes no ser\u00e1n reemplazados.", + "ExtractChapterImagesHelp": "Extraer im\u00e1genes de cap\u00edtulo permitir\u00e1 a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, uso de CPU intensivo y puede requerir varios gigabytes de espacio. Se ejecuta como una tarea nocturna, a las 4 de la ma\u00f1ana, aunque esto se puede configurar en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante las horas pico.", + "LabelMetadataDownloadLanguage": "Idioma preferido:", + "ButtonAutoScroll": "Auto-desplazamiento", + "LabelImageSavingConvention": "Sistema de guardado de im\u00e1genes:", + "LabelImageSavingConventionHelp": "Media Browser reconoce im\u00e1genes de la mayor\u00eda de aplicaciones de medios. La elecci\u00f3n de su sistema de descarga es \u00fatil si tambi\u00e9n usa otros productos.", + "OptionImageSavingCompatible": "Compatible - MB3\/Plex\/Xbmc", + "OptionImageSavingStandard": "Est\u00e1ndard - MB3\/MB2", + "ButtonSignIn": "Registrarse", + "TitleSignIn": "Registrarse", + "HeaderPleaseSignIn": "Por favor reg\u00edstrese", + "LabelUser": "Usuario:", + "LabelPassword": "Contrase\u00f1a:", + "ButtonManualLogin": "Registro manual:", + "PasswordLocalhostMessage": "No se necesitan contrase\u00f1as al iniciar sesi\u00f3n desde localhost.", + "TabGuide": "Gu\u00eda", + "TabChannels": "Canales", + "TabCollections": "Colecciones", + "HeaderChannels": "Canales", + "TabRecordings": "Grabaciones", + "TabScheduled": "Programado", + "TabSeries": "Series", + "TabFavorites": "Favoritos", + "TabMyLibrary": "Mi biblioteca", + "ButtonCancelRecording": "Cancelar grabaci\u00f3n", + "HeaderPrePostPadding": "Pre\/post grabaci\u00f3n extra", + "LabelPrePaddingMinutes": "Minutos previos extras:", + "OptionPrePaddingRequired": "Minutos previos extras requeridos para grabar.", + "LabelPostPaddingMinutes": "Minutos extras post grabaci\u00f3n:", + "OptionPostPaddingRequired": "Minutos post grabaci\u00f3n extras requeridos para grabar.", + "HeaderWhatsOnTV": "Que hacen ahora", + "HeaderUpcomingTV": "Pr\u00f3ximos programas", + "TabStatus": "Estado", + "TabSettings": "Opciones", + "ButtonRefreshGuideData": "Actualizar datos de la gu\u00eda", + "OptionPriority": "Prioridad", + "OptionRecordOnAllChannels": "Grabar programa en cualquier canal", + "OptionRecordAnytime": "Grabar programa a cualquier hora", + "OptionRecordOnlyNewEpisodes": "Grabar s\u00f3lo nuevos episodios", + "HeaderDays": "D\u00edas", + "HeaderActiveRecordings": "Grabaciones activas", + "HeaderLatestRecordings": "\u00daltimas grabaciones", + "HeaderAllRecordings": "Todas la grabaciones", + "ButtonPlay": "Reproducir", + "ButtonEdit": "Editar", + "ButtonRecord": "Grabar", + "ButtonDelete": "Borrar", + "ButtonRemove": "Quitar", + "OptionRecordSeries": "Grabar serie", + "HeaderDetails": "Detalles", + "TitleLiveTV": "Tv en vivo", + "LabelNumberOfGuideDays": "N\u00famero de d\u00edas de descarga de la gu\u00eda.", + "LabelNumberOfGuideDaysHelp": "Descargar m\u00e1s d\u00edas de la gu\u00eda ofrece la posibilidad de programar grabaciones con mayor antelaci\u00f3n y ver m\u00e1s listas, pero tambi\u00e9n tarda m\u00e1s en descargarse. Auto elegir\u00e1 en funci\u00f3n del n\u00famero de canales.", + "LabelActiveService": "Activar servicio", + "LabelActiveServiceHelp": "Es posible instalar m\u00faltiples plugins de tv, pero s\u00f3lo puede estar activo uno a la vez.", + "OptionAutomatic": "Auto", + "LiveTvPluginRequired": "El servicio de TV en vivo es necesario para poder continuar.", + "LiveTvPluginRequiredHelp": "Instale uno de los plugins disponibles, como Next Pvr o ServerVmc.", + "LabelCustomizeOptionsPerMediaType": "Personalizar por tipo de medio:", + "OptionDownloadThumbImage": "Miniatura", + "OptionDownloadMenuImage": "Men\u00fa", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadBoxImage": "Caja", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadBannerImage": "Pancarta", + "OptionDownloadBackImage": "Atr\u00e1s", + "OptionDownloadArtImage": "Arte", + "OptionDownloadPrimaryImage": "Principal", + "HeaderFetchImages": "Buscar im\u00e1genes", + "HeaderImageSettings": "Opciones de im\u00e1gen", + "TabOther": "Otros", + "LabelMaxBackdropsPerItem": "M\u00e1ximo n\u00famero de im\u00e1genes de fondo por \u00edtem:", + "LabelMaxScreenshotsPerItem": "M\u00e1ximo n\u00famero de capturas de pantalla por \u00edtem:", + "LabelMinBackdropDownloadWidth": "Anchura m\u00ednima de descarga de im\u00e1genes de fondo:", + "LabelMinScreenshotDownloadWidth": "Anchura m\u00ednima de descarga de capturas de pantalla:", + "ButtonAddScheduledTaskTrigger": "A\u00f1adir eventos", + "HeaderAddScheduledTaskTrigger": "A\u00f1adir eventos de ejecuci\u00f3n", + "ButtonAdd": "A\u00f1adir", + "LabelTriggerType": "Tipo de evento:", + "OptionDaily": "Diario", + "OptionWeekly": "Semanal", + "OptionOnInterval": "En un intervalo", + "OptionOnAppStartup": "Al iniciar la aplicaci\u00f3n", + "OptionAfterSystemEvent": "Despu\u00e9s de un evento de sistema", + "LabelDay": "D\u00eda:", + "LabelTime": "Hora:", + "LabelEvent": "Evento:", + "OptionWakeFromSleep": "Despertar", + "LabelEveryXMinutes": "Cada:", + "HeaderTvTuners": "Sintonizadores", + "HeaderGallery": "Galer\u00eda", + "HeaderLatestGames": "\u00daltimos Juegos", + "HeaderRecentlyPlayedGames": "Juegos utilizados recientemente", + "TabGameSystems": "Sistema de Juego", + "TitleMediaLibrary": "Librer\u00eda de medios", + "TabFolders": "Carpetas", + "TabPathSubstitution": "Ruta alternativa", + "LabelSeasonZeroDisplayName": "Nombre de la Temporada 0:", + "LabelEnableRealtimeMonitor": "Activar monitoreo en tiempo real", + "LabelEnableRealtimeMonitorHelp": "Los cambios se procesar\u00e1n inmediatamente, en sistemas de archivo que lo soporten.", + "ButtonScanLibrary": "Escanear Librer\u00eda", + "HeaderNumberOfPlayers": "Jugadores:", + "OptionAnyNumberOfPlayers": "Cualquiera", + "Option1Player": "1+", + "Option2Player": "2+", + "Option3Player": "3+", + "Option4Player": "4+", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderThemeVideos": "V\u00eddeos de tema", + "HeaderThemeSongs": "Canciones de tema", + "HeaderScenes": "Escenas", + "HeaderAwardsAndReviews": "Premios y reconocimientos", + "HeaderSoundtracks": "Pistas de audio", + "HeaderMusicVideos": "V\u00eddeos musicales", + "HeaderSpecialFeatures": "Caracter\u00edsticas especiales", + "HeaderCastCrew": "Reparto y equipo t\u00e9cnico", + "HeaderAdditionalParts": "Partes adicionales", + "ButtonSplitVersionsApart": "Dividir versiones aparte", + "ButtonPlayTrailer": "Trailer", + "LabelMissing": "Falta", + "LabelOffline": "Apagado", + "PathSubstitutionHelp": "Las rutas alternativas se utilizan para mapear una ruta en el servidor a la que los clientes puedan acceder. El permitir que los clientes se conecten directamente a trav\u00e9s de la red y puedan reproducir los medios directamente, evita utilizar recursos del servidor para la transcodificaci\u00f3n y el stream,", + "HeaderFrom": "Desde", + "HeaderTo": "Hasta", + "LabelFrom": "Desde:", + "LabelFromHelp": "Ejemplo: D:\\Pel\u00edculas (en el servidor)", + "LabelTo": "Hasta:", + "LabelToHelp": "Ejemplo: \\\\MiServidor\\Pel\u00edculas (ruta a la que puedan acceder los clientes)", + "ButtonAddPathSubstitution": "A\u00f1adir ruta alternativa", + "OptionSpecialEpisode": "Especiales", + "OptionMissingEpisode": "Episodios que faltan", + "OptionUnairedEpisode": "Episodios no emitidos", + "OptionEpisodeSortName": "Nombre corto del episodio", + "OptionSeriesSortName": "Nombre de la serie", + "OptionTvdbRating": "Valoraci\u00f3n tvdb", + "HeaderTranscodingQualityPreference": "Preferencia de calidad de transcodificaci\u00f3n:", + "OptionAutomaticTranscodingHelp": "El servidor decidir\u00e1 la calidad y la velocidad", + "OptionHighSpeedTranscodingHelp": "Calidad menor, pero codificaci\u00f3n r\u00e1pida", + "OptionHighQualityTranscodingHelp": "C\u00e1lidad mayor, pero codificaci\u00f3n lenta", + "OptionMaxQualityTranscodingHelp": "La mayor calidad posible con codificaci\u00f3n lenta y alto uso de CPU", + "OptionHighSpeedTranscoding": "Mayor velocidad", + "OptionHighQualityTranscoding": "Mayor calidad", + "OptionMaxQualityTranscoding": "M\u00e1xima calidad", + "OptionEnableDebugTranscodingLogging": "Activar el registro de depuraci\u00f3n del transcodificador", + "OptionEnableDebugTranscodingLoggingHelp": "Esto crear\u00e1 archivos de registro muy grandes y s\u00f3lo se recomienda cuando sea necesario para solucionar problemas.", + "OptionUpscaling": "Permitir que los clientes soliciten v\u00eddeo upscaled", + "OptionUpscalingHelp": "En algunos casos esto se traducir\u00e1 en una mejora de la calidad del v\u00eddeo, pero aumentar\u00e1 el uso de CPU.", + "EditCollectionItemsHelp": "Agregar o quitar pel\u00edculas, series, discos, libros o juegos que desee agrupar dentro de esta colecci\u00f3n.", + "HeaderAddTitles": "A\u00f1adir T\u00edtulos", + "LabelEnableDlnaPlayTo": "Actvar la reproducci\u00f3n en DLNAi", + "LabelEnableDlnaPlayToHelp": "Media Browser puede detectar dispositivos en su red y ofrecer la posibilidad de controlarlos remotamente.", + "LabelEnableDlnaDebugLogging": "Activar el registro de depuraci\u00f3n de DLNA", + "LabelEnableDlnaDebugLoggingHelp": "Esto crear\u00e1 archivos de registro de gran tama\u00f1o y s\u00f3lo debe ser utilizado cuando sea necesario para solucionar problemas.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de detecci\u00f3n de cliente (segundos)", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duraci\u00f3n en segundos del intervalo entre las b\u00fasquedas SSDP realizadas por Media Browser.", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", + "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de s\u00f3lo lectura. Para anular un perfil del sistema, crear un perfil personalizado del mismo dispositivo.", + "TitleDashboard": "Panel de control", + "TabHome": "Inicio", + "TabInfo": "Info", + "HeaderLinks": "Enlaces", + "HeaderSystemPaths": "Rutas del sistema", + "LinkCommunity": "Comunidad", + "LinkGithub": "Github", + "LinkApiDocumentation": "Documentaci\u00f3n API", + "LabelFriendlyServerName": "Nombre informal del servidor:", + "LabelFriendlyServerNameHelp": "Este nombre se podr\u00e1 utilizar para identificar este servidor. Si se deja en blanco se usar\u00e1 el nombre del ordenador.", + "LabelPreferredDisplayLanguage": "Idioma de pantalla preferido", + "LabelPreferredDisplayLanguageHelp": "La traducci\u00f3n de Media Browser es un proyecto en curso y a\u00fan no est\u00e1 completado.", + "LabelReadHowYouCanContribute": "Lea acerca de c\u00f3mo usted puede contribuir.", + "HeaderNewCollection": "Nueva colecci\u00f3n", + "HeaderAddToCollection": "A\u00f1adir a la colecci\u00f3n", + "ButtonSubmit": "Enviar", + "NewCollectionNameExample": "Ejemplo: Star Wars Colecci\u00f3n", + "OptionSearchForInternetMetadata": "Buscar en internet ilustraciones y metadatos", + "ButtonCreate": "Crear", + "LabelHttpServerPortNumber": "Puerto Http del servidor:", + "LabelWebSocketPortNumber": "N\u00famero de puerto WebSocket:", + "LabelEnableAutomaticPortHelp": "UPnP permite automatizar la configuraci\u00f3n del router para el acceso remoto. Esto puede no funcionar en algunos modelos de router.", + "LabelExternalDDNS": "DDNS externa:", + "LabelExternalDDNSHelp": "Si dispone de DNS din\u00e1mica, escr\u00edbala aqu\u00ed. Media Brower la utilizar\u00e1 para las conexiones remotas.", + "TabResume": "Continuar", + "TabWeather": "El tiempo", + "TitleAppSettings": "Opciones de la App", + "LabelMinResumePercentage": "Porcentaje m\u00ednimo para reanudaci\u00f3n:", + "LabelMaxResumePercentage": "Porcentaje m\u00e1ximo para reanudaci\u00f3n::", + "LabelMinResumeDuration": "Duraci\u00f3n m\u00ednima de reanudaci\u00f3n (segundos):", + "LabelMinResumePercentageHelp": "Los t\u00edtulos se asumir\u00e1n como no reproducidos si se paran antes de este momento", + "LabelMaxResumePercentageHelp": "Los t\u00edtulos se asumir\u00e1n como reproducidos si se paran despu\u00e9s de este momento", + "LabelMinResumeDurationHelp": "Los t\u00edtulos m\u00e1s cortos de esto no ser\u00e1n reanudables", + "TitleAutoOrganize": "Organizaci\u00f3n autom\u00e1tica", + "TabActivityLog": "Log de actividad", + "HeaderName": "Nombre", + "HeaderDate": "Fecha", + "HeaderSource": "Origen", + "HeaderDestination": "Destino", + "HeaderProgram": "Programa", + "HeaderClients": "Clientes", + "LabelCompleted": "Completado", + "LabelFailed": "Err\u00f3neo", + "LabelSkipped": "Omitido", + "HeaderEpisodeOrganization": "Organizaci\u00f3n de episodios", + "LabelSeries": "Serie:", + "LabelSeasonNumber": "Temporada n\u00famero:", + "LabelEpisodeNumber": "Episodio n\u00famero:", + "LabelEndingEpisodeNumber": "N\u00famero episodio final:", + "LabelEndingEpisodeNumberHelp": "S\u00f3lo requerido para archivos multi-episodio", + "HeaderSupportTheTeam": "Apoye al Equipo de Media Browser", + "LabelSupportAmount": "Importe (USD)", + "HeaderSupportTheTeamHelp": "Ayude a garantizar el desarrollo continuo de este proyecto mediante una donaci\u00f3n. Una parte de todas las donaciones ir\u00e1n a parar a otras herramientas gratuitas de las que dependemos.", + "ButtonEnterSupporterKey": "Entre la Key de Seguidor", + "DonationNextStep": "Cuando haya terminado, vuelva y entre su key de seguidor que recibir\u00e1 por email.", + "AutoOrganizeHelp": "Organizaci\u00f3n autom\u00e1tica monitoriza sus carpetas de descarga en busca de nuevos archivos y los mueve a sus directorios de medios.", + "AutoOrganizeTvHelp": "La organizaci\u00f3n de archivos de TV s\u00f3lo a\u00f1adir\u00e1 episodios a series existentes. No crear\u00e1 carpetas para series nuevas.", + "OptionEnableEpisodeOrganization": "Activar la organizaci\u00f3n de nuevos episodios", + "LabelWatchFolder": "Ver carpeta:", + "LabelWatchFolderHelp": "El servidor sondear\u00e1 esta carpeta durante la tarea programada \"Organizar nuevos archivos de medios\".", + "ButtonViewScheduledTasks": "Ver tareas programadas", + "LabelMinFileSizeForOrganize": "Tama\u00f1o m\u00ednimo de archivo (MB):", + "LabelMinFileSizeForOrganizeHelp": "Los archivos menores de este tama\u00f1po se ignorar\u00e1n.", + "LabelSeasonFolderPattern": "Patr\u00f3n de la carpeta para temporadas:", + "LabelSeasonZeroFolderName": "Nombre de la carpeta para la temporada cero:", + "HeaderEpisodeFilePattern": "Patr\u00f3n para archivos de episodio", + "LabelEpisodePattern": "Patr\u00f3n de episodio:", + "LabelMultiEpisodePattern": "Patr\u00f3n para multi-episodio:", + "HeaderSupportedPatterns": "Patrones soportados", + "HeaderTerm": "Plazo", + "HeaderPattern": "Patr\u00f3n", + "HeaderResult": "Resultado", + "LabelDeleteEmptyFolders": "Borrar carpetas vacias despu\u00e9s de la organizaci\u00f3n", + "LabelDeleteEmptyFoldersHelp": "Activar para mantener el directorio de descargas limpio.", + "LabelDeleteLeftOverFiles": "Eliminar los archivos sobrantes con las siguientes extensiones:", + "LabelDeleteLeftOverFilesHelp": "Separar con ;. Por ejemplo: .nfo;.txt", + "OptionOverwriteExistingEpisodes": "Sobreescribir episodios ya existentes", + "LabelTransferMethod": "M\u00e9todo de transferencia", + "OptionCopy": "Copiar", + "OptionMove": "Mover", + "LabelTransferMethodHelp": "Copiar o mover archivos desde la carpeta de inspecci\u00f3n", + "HeaderLatestNews": "Ultimas noticias", + "HeaderHelpImproveMediaBrowser": "Ayuda a mejorar Media Browser", + "HeaderRunningTasks": "Tareas en ejecuci\u00f3n", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderPendingInstallations": "Instalaciones pendientes", + "HeaerServerInformation": "Informaci\u00f3n del servidor", + "ButtonRestartNow": "Reiniciar ahora", + "ButtonRestart": "Reiniciar", + "ButtonShutdown": "Apagar", + "ButtonUpdateNow": "Actualizar ahora", + "PleaseUpdateManually": "Por favor cierre el servidor y actualice manualmente.", + "NewServerVersionAvailable": "\u00a1Hay disponible una nueva versi\u00f3n de Media Browser Server!", + "ServerUpToDate": "Media Browser Server est\u00e1 actualizado", + "ErrorConnectingToMediaBrowserRepository": "Hubo un error al conectarse remotamente al repositorio de Media Browser,", + "LabelComponentsUpdated": "Los componentes siguientes se han instalado o actualizado:", + "MessagePleaseRestartServerToFinishUpdating": "Reinicie el servidor para acabar de aplicar las actualizaciones.", + "LabelDownMixAudioScale": "Escala de reducci\u00f3n de potencia de audio:", + "LabelDownMixAudioScaleHelp": "Potenciador de audio. Establecer a 1 para preservar el volumen original.", + "ButtonLinkKeys": "Enlazar claves", + "LabelOldSupporterKey": "Antigua clave de seguidor", + "LabelNewSupporterKey": "Nueva clave de seguidor", + "HeaderMultipleKeyLinking": "Vinculaci\u00f3n de m\u00faltiples claves", + "MultipleKeyLinkingHelp": "Si usted tiene m\u00e1s de una clave de seguidor, utilice este formulario para vincular los registros de la antigua clave con la nueva.", + "LabelCurrentEmailAddress": "Cuenta de correo actual", + "LabelCurrentEmailAddressHelp": "La direcci\u00f3n de correo electr\u00f3nico actual a la que se envi\u00f3 la nueva clave.", + "HeaderForgotKey": "Perd\u00ed mi clave", + "LabelEmailAddress": "Direcci\u00f3n de correo", + "LabelSupporterEmailAddress": "La direcci\u00f3n de correo que utliz\u00f3 para comprar la clave.", + "ButtonRetrieveKey": "Recuperar clave", + "LabelSupporterKey": "Clave de seguidor (pegar desde el correo)", + "LabelSupporterKeyHelp": "Entre su clave de seguidor para empezar a disfrutar de los beneficios adicionales que la comunidad ha creado para Media Browser.", + "MessageInvalidKey": "La clave MB3 falta o es inv\u00e1lida", + "ErrorMessageInvalidKey": "Para acceder al contenido premium debe registrarse, tambi\u00e9n debe ser un MB3 Seguidor. Por favor, done y apoye el desarrollo continuado del producto principal. Gracias.", + "HeaderDisplaySettings": "Opciones de pantalla", + "TabPlayTo": "Reproducir en", + "LabelEnableDlnaServer": "Habilitar servidor Dlna", + "LabelEnableDlnaServerHelp": "Permite que los dispositivos UPnp de su red puedan navegar y repoducir contenidos de Media Browser.", + "LabelEnableBlastAliveMessages": "Explotar mensajes en vivo", + "LabelEnableBlastAliveMessagesHelp": "Active aqu\u00ed si el servidor no es detectado correctamente por otros dispositivos UPnP en su red.", + "LabelBlastMessageInterval": "Intervalo para mensajes en vivo (segundos)", + "LabelBlastMessageIntervalHelp": "Determina la duraci\u00f3n en segundos entre los mensajes en vivo del servidor .", + "LabelDefaultUser": "Usuario por defecto:", + "LabelDefaultUserHelp": "Determina de q\u00fae usuario se utilizar\u00e1 su biblioteca de medios para mostrarla por defecto en los dipositivos conectados. Esto puede cambiarse para cada dispositivo mediante el uso de perfiles.", + "TitleDlna": "DLNA", + "TitleChannels": "Canales", + "HeaderServerSettings": "Ajustes del Servidor", + "LabelWeatherDisplayLocation": "Lugar del que mostar el tiempo:", + "LabelWeatherDisplayLocationHelp": "C\u00f3digo postal USA \/ Ciudad, Estado, Pa\u00eds \/ Ciudad, Pa\u00eds", + "LabelWeatherDisplayUnit": "Unidad de media para la temperatura:", + "OptionCelsius": "Celsius", + "OptionFahrenheit": "Fahrenheit", + "HeaderRequireManualLogin": "Requerir entrada de usuario manual para:", + "HeaderRequireManualLoginHelp": "Cuando est\u00e1 desactivado los clientes saldr\u00e1n en la pantalla de inicio para seleccionarlos visualmente.", + "OptionOtherApps": "Otras aplicaciones", + "OptionMobileApps": "Aplicaciones m\u00f3viles", + "HeaderNotificationList": "Haga click en una notificaci\u00f3n para configurar sus opciones de env\u00edo.", + "NotificationOptionApplicationUpdateAvailable": "Disponible actualizaci\u00f3n de la aplicaci\u00f3n", + "NotificationOptionApplicationUpdateInstalled": "Se ha instalado la actualizaci\u00f3n de la aplicaci\u00f3n", + "NotificationOptionPluginUpdateInstalled": "Se ha instalado la actualizaci\u00f3n del plugin", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionVideoPlayback": "Reproducci\u00f3n de video", + "NotificationOptionAudioPlayback": "Reproducci\u00f3n de audio", + "NotificationOptionGamePlayback": "Iniciar juegos", + "NotificationOptionVideoPlaybackStopped": "Reproducci\u00f3n de video detenida", + "NotificationOptionAudioPlaybackStopped": "Reproducci\u00f3n de audio detenida", + "NotificationOptionGamePlaybackStopped": "Reproducci\u00f3n de juego detenida", + "NotificationOptionTaskFailed": "La tarea programada ha fallado", + "NotificationOptionInstallationFailed": "Fallo en la instalaci\u00f3n", + "NotificationOptionNewLibraryContent": "Nuevo contenido a\u00f1adido", + "NotificationOptionNewLibraryContentMultiple": "Nuevo contenido a\u00f1adido (multiple)", + "SendNotificationHelp": "Por defecto, las notificaciones aparecer\u00e1n en el panel de control. Compruebe el cat\u00e1logo de plugins para instalar opciones adicionales para las notificaciones.", + "NotificationOptionServerRestartRequired": "Se requiere el reinicio del servidor", + "LabelNotificationEnabled": "Activar esta notificaci\u00f3n", + "LabelMonitorUsers": "Supervisar la actividad de:", + "LabelSendNotificationToUsers": "Enviar la notificaci\u00f3n a:", + "UsersNotNotifiedAboutSelfActivity": "Los usuarios no ser\u00e1n notificados acerca de sus propias actividades.", + "LabelUseNotificationServices": "Usar los siguientes servicios:", + "CategoryUser": "Usuario", + "CategorySystem": "Sistema", + "CategoryApplication": "Aplicaci\u00f3n", + "CategoryPlugin": "Plugin", + "LabelMessageTitle": "T\u00edtulo del mensaje:", + "LabelAvailableTokens": "Tokens disponibles:", + "AdditionalNotificationServices": "Visite el cat\u00e1logo de plugins para instalar servicios de notificaci\u00f3n adicionales.", + "OptionAllUsers": "Todos los usuarios", + "OptionAdminUsers": "Administradores", + "OptionCustomUsers": "A medida", + "ButtonArrowUp": "Arriba", + "ButtonArrowDown": "Abajo", + "ButtonArrowLeft": "Izquierda", + "ButtonArrowRight": "Derecha", + "ButtonBack": "Atr\u00e1s", + "ButtonInfo": "Info", + "ButtonOsd": "Visualizaci\u00f3n en pantalla", + "ButtonPageUp": "P\u00e1gina arriba", + "ButtonPageDown": "P\u00e1gina abajo", + "PageAbbreviation": "PG", + "ButtonHome": "Inicio", + "ButtonSettings": "Opciones", + "ButtonTakeScreenshot": "Captura de pantalla", + "ButtonLetterUp": "Letter arriba", + "ButtonLetterDown": "Letter abajo", + "PageButtonAbbreviation": "PG", + "LetterButtonAbbreviation": "A", + "TabNowPlaying": "Reproduciendo ahora", + "TabNavigation": "Navegaci\u00f3n", + "TabControls": "Controles", + "ButtonFullscreen": "Pantalla completa", + "ButtonScenes": "Escenas", + "ButtonSubtitles": "Subt\u00edtulos", + "ButtonAudioTracks": "Pistas de audio", + "ButtonPreviousTrack": "Pista anterior", + "ButtonNextTrack": "Pista siguiente", + "ButtonStop": "Detener", + "ButtonPause": "Pausa", + "LabelGroupMoviesIntoCollections": "Agrupar pel\u00edculas en colecciones", + "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran las listas de pel\u00edculas, las pel\u00edculas pertenecientes a una colecci\u00f3n se mostrar\u00e1n como un elemento agrupado.", + "NotificationOptionPluginError": "Error en plugin", + "ButtonVolumeUp": "Subir volumen", + "ButtonVolumeDown": "Bajar volumen", + "ButtonMute": "Silencio", + "HeaderLatestMedia": "\u00daltimos medios", + "OptionSpecialFeatures": "Caracter\u00edsticas especiales", + "HeaderCollections": "Colecciones", + "LabelProfileCodecsHelp": "Separados por comas. Esto se puede dejar vac\u00edo para aplicar a todos los codecs.", + "LabelProfileContainersHelp": "Separados por comas. Esto se puede dejar vac\u00edo para aplicar a todos los contenedores.", + "HeaderResponseProfile": "Perfil de respuesta", + "LabelType": "Tipo:", + "LabelProfileContainer": "Contenedor:", + "LabelProfileVideoCodecs": "Codecs de video:", + "LabelProfileAudioCodecs": "Codecs de audio:", + "LabelProfileCodecs": "Codecs:", + "HeaderDirectPlayProfile": "Perfil de reproducci\u00f3n directa", + "HeaderTranscodingProfile": "Perfil de transcodificaci\u00f3n", + "HeaderCodecProfile": "Perfil de codec", + "HeaderCodecProfileHelp": "Perfiles de codec indican las limitaciones de un dispositivo cuando se reproducen codecs espec\u00edficos. Si se aplica una limitaci\u00f3n entonces el medio se transcodificar\u00e1, incluso si el codec est\u00e1 configurado para reproducci\u00f3n directa.", + "HeaderContainerProfile": "Perfil de contenedor", + "HeaderContainerProfileHelp": "Perfiles de codec indican las limitaciones de un dispositivo mientras reproduce formatos espec\u00edficos. If se aplica una limitaci\u00f3n entonces el medio se transcodificar\u00e1, incluso si el formato est\u00e1 configurado para reproducci\u00f3n directa.", + "OptionProfileVideo": "Video", + "OptionProfileAudio": "Audio", + "OptionProfileVideoAudio": "Video audio", + "OptionProfilePhoto": "Foto", + "LabelUserLibrary": "Librer\u00eda de usuario:", + "LabelUserLibraryHelp": "Seleccione de qu\u00e9 usuario se utilizar\u00e1 la librer\u00eda en el dispositivo. D\u00e9jelo vac\u00edo para utilizar la configuraci\u00f3n por defecto.", + "OptionPlainStorageFolders": "Ver todas las carpetas como carpetas de almacenamiento sin formato.", + "OptionPlainStorageFoldersHelp": "Si est\u00e1 activado, todas las carpetas se representan en DIDL como \"object.container.storageFolder\" en lugar de un tipo m\u00e1s espec\u00edfico, como por ejemplo \"object.container.person.musicArtist\".", + "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video sin formato", + "OptionPlainVideoItemsHelp": "Si est\u00e1 habilitado, todos los v\u00eddeos est\u00e1n representados en DIDL como \"object.item.videoItem\" en lugar de un tipo m\u00e1s espec\u00edfico, como por ejemplo \"object.item.videoItem.movie\".", + "LabelSupportedMediaTypes": "Tipos de medio soportados:", + "TabIdentification": "Identificaci\u00f3n", + "TabDirectPlay": "Reproducci\u00f3n directa", + "TabContainers": "Contenedores", + "TabCodecs": "Codecs", + "TabResponses": "Respuestas", + "HeaderProfileInformation": "Informaci\u00f3n del perfil", + "LabelEmbedAlbumArtDidl": "Incorporar la car\u00e1tula del \u00e1lbum en didl", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este m\u00e9todo para obtener la car\u00e1tula del \u00e1lbum. Otros pueden fallar al reproducir con esta opci\u00f3n habilitada.", + "LabelAlbumArtPN": "Car\u00e1tula del album PN:", + "LabelAlbumArtHelp": "PN utilizado para la car\u00e1tula del \u00e1lbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos clientes requieren un valor espec\u00edfico, independientemente del tama\u00f1o de la imagen.", + "LabelAlbumArtMaxWidth": "Anchura m\u00e1xima de la car\u00e1tula del album:", + "LabelAlbumArtMaxWidthHelp": "Resoluci\u00f3n m\u00e1xima de la car\u00e1tula del \u00e1lbum expuesta a trav\u00e9s de upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Altura m\u00e1xima de la car\u00e1tula del album:", + "LabelAlbumArtMaxHeightHelp": "Resoluci\u00f3n m\u00e1xima de la car\u00e1tula del \u00e1lbum expuesta a trav\u00e9s de upnp:albumArtURI.", + "LabelIconMaxWidth": "Anchura m\u00e1xima de icono:", + "LabelIconMaxWidthHelp": "Resoluci\u00f3n m\u00e1xima de los iconos expuestos via upnp:icon.", + "LabelIconMaxHeight": "Altura m\u00e1xima de icono:", + "LabelIconMaxHeightHelp": "Resoluci\u00f3n m\u00e1xima de los iconos expuestos via upnp:icon.", + "LabelIdentificationFieldHelp": "Una subcadena insensible a may\u00fasculas o min\u00fasculas o una expresi\u00f3n regex.", + "HeaderProfileServerSettingsHelp": "Estos valores controlan el modo en que Media Browser se presentar\u00e1 en el dispositivo.", + "LabelMaxBitrate": "Bitrate m\u00e1ximo:", + "LabelMaxBitrateHelp": "Especificar una tasa de bits m\u00e1xima en entornos de ancho de banda limitado, o si el dispositivo impone su propio l\u00edmite.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar las solicitudes de intervalo de bytes de transcodificaci\u00f3n", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si est\u00e1 activado, estas solicitudes ser\u00e1n atendidas pero ignorar\u00e1n el encabezado de intervalo de bytes.", + "LabelFriendlyName": "Nombre amigable", + "LabelManufacturer": "Fabricante", + "LabelManufacturerUrl": "Url del fabricante", + "LabelModelName": "Nombre de modelo", + "LabelModelNumber": "N\u00famero de modelo", + "LabelModelDescription": "Descripci\u00f3n de modelo", + "LabelModelUrl": "Url del modelo", + "LabelSerialNumber": "N\u00famero de serie", + "LabelDeviceDescription": "Descripci\u00f3n del dispositivo", + "HeaderIdentificationCriteriaHelp": "Entre al menos un criterio de identificaci\u00f3n.", + "HeaderDirectPlayProfileHelp": "A\u00f1adir perfiles de reproducci\u00f3n directa para indicar qu\u00e9 formatos puede utilizar el dispositivo de forma nativa.", + "HeaderTranscodingProfileHelp": "A\u00f1adir perfiles de transcodificaci\u00f3n para indicar qu\u00e9 formatos se deben utilizar cuando se requiera transcodificaci\u00f3n.", + "HeaderResponseProfileHelp": "Perfiles de respuesta proporcionan una forma de personalizar la informaci\u00f3n que se env\u00eda al dispositivo cuando se reproducen ciertos tipos de medios.", + "LabelXDlnaCap": "X-Dlna cap:", + "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el espacio de nombre urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaDoc": "X-Dlna doc:", + "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el espacio de nombreurn:schemas-dlna-org:device-1-0.", + "LabelSonyAggregationFlags": "Agregaci\u00f3n de banderas Sony:", + "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el espacio de nombre urn:schemas-sonycom:av.", + "LabelTranscodingContainer": "Contenedor:", + "LabelTranscodingVideoCodec": "Codec de video:", + "LabelTranscodingVideoProfile": "Perfil de video:", + "LabelTranscodingAudioCodec": "Codec de audio:", + "OptionEnableM2tsMode": "Activar modo M2ts", + "OptionEnableM2tsModeHelp": "Activar modo m2ts cuando se codifique a mpegts", + "OptionEstimateContentLength": "Estimar longitud del contenido al transcodificar", + "OptionReportByteRangeSeekingWhenTranscoding": "Indicar que el servidor soporta la b\u00fasqueda de byte al transcodificar", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es necesario para algunos dispositivos que no buscan el tiempo muy bien.", + "HeaderSubtitleDownloadingHelp": "Cuando Media Browser escanea los archivos de v\u00eddeo, puede buscar subt\u00edtulos faltantes y descargarlos usando un proveedor de subt\u00edtulos como OpenSubtitles.org.", + "HeaderDownloadSubtitlesFor": "Descarga subt\u00edtulos para:", + "MessageNoChapterProviders": "Instalar un plugin proveedor de cap\u00edtulos como ChapterDb o tagChimp para permitir opciones de cap\u00edtulo adicionales.", + "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subt\u00edtulos gr\u00e1ficos", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de los subt\u00edtulos se traducir\u00e1 en una prestaci\u00f3n m\u00e1s eficiente para los clientes m\u00f3viles.", + "TabSubtitles": "Subt\u00edtulos", + "TabChapters": "Cap\u00edtulos", + "HeaderDownloadChaptersFor": "Descarga nombres de los cap\u00edtulos de:", + "LabelOpenSubtitlesUsername": "Usuario de Open Subtitles:", + "LabelOpenSubtitlesPassword": "Contrase\u00f1a de Open Subtitles:", + "HeaderChapterDownloadingHelp": "Cuando Media Browser escanea los archivos de v\u00eddeo, puede descargar nombres de los cap\u00edtulos desde la red utilizando plugins de cap\u00edtulos como ChapterDb y tagChimp.", + "LabelPlayDefaultAudioTrack": "\nReproducir pista de audio predeterminado, independientemente del idioma", + "LabelSubtitlePlaybackMode": "Modo de Subt\u00edtulo:", + "LabelDownloadLanguages": "Idiomas de descarga:", + "ButtonRegister": "Registrar", + "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", + "LabelSkipIfAudioTrackPresentHelp": "Desactive esta opci\u00f3n para asegurar que todos los v\u00eddeos tienen subt\u00edtulos, sin importar el idioma de audio.", + "HeaderSendMessage": "Enviar mensaje", + "ButtonSend": "Enviar", + "LabelMessageText": "Mensaje de texto:", + "MessageNoAvailablePlugins": "No hay plugins disponibles.", + "LabelDisplayPluginsFor": "Mostrar plugins para:", + "PluginTabMediaBrowserClassic": "MB Classic", + "PluginTabMediaBrowserTheater": "MB Theater", + "TabOtherPlugins": "Otros", + "LabelEpisodeName": "Nombre episodio", + "LabelSeriesName": "Nombre de la serie", + "ValueSeriesNamePeriod": "Series.name", + "ValueSeriesNameUnderscore": "Series_name", + "ValueEpisodeNamePeriod": "Episode.name", + "ValueEpisodeNameUnderscore": "Episode_name", + "HeaderTypeText": "Entrar texto", + "LabelTypeText": "Texto", + "HeaderSearchForSubtitles": "B\u00fasqueda de Subt\u00edtulos", + "MessageNoSubtitleSearchResultsFound": "No se han encontrado resultados en la b\u00fasqueda.", + "TabDisplay": "Pantalla", + "TabLanguages": "Idiomas", + "TabWebClient": "Cliente web", + "LabelEnableThemeSongs": "Habilitar temas musicales", + "LabelEnableBackdrops": "Habilitar im\u00e1genes de fondo", + "LabelEnableThemeSongsHelp": "Si est\u00e1 habilitado, se reproducir\u00e1n temas musicales de fondo mientras navega por la biblioteca.", + "LabelEnableBackdropsHelp": "Si est\u00e1 habilitado, se mostrar\u00e1n im\u00e1genes de fondo en algunas p\u00e1ginas mientras navega por la biblioteca.", + "HeaderHomePage": "P\u00e1gina de inicio", + "HeaderSettingsForThisDevice": "Opciones para este dispositivo", + "OptionAuto": "Auto", + "OptionYes": "Si", + "OptionNo": "No", + "LabelHomePageSection1": "Secci\u00f3n uno de la p\u00e1gina de inicio:", + "LabelHomePageSection2": "Secci\u00f3n dos de la p\u00e1gina de inicio:", + "LabelHomePageSection3": "Secci\u00f3n tres de la p\u00e1gina de inicio:", + "LabelHomePageSection4": "Secci\u00f3n cuarta de la p\u00e1gina de inicio", + "OptionMyViewsButtons": "Mis vistas (botones)", + "OptionMyViews": "Mis vistas", + "OptionMyViewsSmall": "Mis vistas (peque\u00f1o)", + "OptionResumablemedia": "Continuar", + "OptionLatestMedia": "\u00daltimos medios", + "OptionLatestChannelMedia": "Ultimos elementos de canales", + "HeaderLatestChannelItems": "Ultimos elementos de canales", + "OptionNone": "Nada", + "HeaderLiveTv": "TV en vivo", + "HeaderReports": "Informes", + "HeaderMetadataManager": "Metadata Manager", + "HeaderPreferences": "Preferencias", + "MessageLoadingChannels": "Cargando contenidos del canal...", + "ButtonMarkRead": "Marcar como le\u00eddo", + "OptionDefaultSort": "Por defecto", + "OptionCommunityMostWatchedSort": "M\u00e1s visto", + "TabNextUp": "Siguiendo", + "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de pel\u00edculas disponibles. Comience ver y calificar sus pel\u00edculas y vuelva para ver las recomendaciones.", + "MessageNoCollectionsAvailable": "Colecciones le permitir\u00e1 disfrutar de grupos personalizados de Pel\u00edculas, Series, Discos, Libros y Juegos. Haga click en el bot\u00f3n nuevo para empezar a crear Colecciones.", + "HeaderWelcomeToMediaBrowserWebClient": "Vienvenido al Cliente Web de Media Browser", + "ButtonDismiss": "Descartar", + "MessageLearnHowToCustomize": "Aprenda c\u00f3mo personalizar esta p\u00e1gina a sus propios gustos personales. Haga clic en su icono de usuario en la esquina superior derecha de la pantalla para ver y actualizar sus preferencias.", + "ButtonEditOtherUserPreferences": "Editar preferencias personales de este usuario.", + "LabelChannelStreamQuality": "Calidad preferida para la transmisi\u00f3n por Internet:", + "LabelChannelStreamQualityHelp": "En un entorno de bajo ancho de banda, limitar la calidad puede ayudar a asegurar una experiencia de streaming suave.", + "OptionBestAvailableStreamQuality": "Mejor disponible", + "LabelEnableChannelContentDownloadingFor": "Habilitar descargas de contenido para el canal:", + "LabelEnableChannelContentDownloadingForHelp": "Algunos canales soportan descargar contenido antes de ver. Habilite esta en ambientes de poco ancho de banda para descargar el contenido del canal durante las horas libres. El contenido se descarga como parte de la tarea programada de descargas de canal.", + "LabelChannelDownloadPath": "Ruta para descargas de contenidos de canales:", + "LabelChannelDownloadPathHelp": "Especifique una ruta personalizada si lo desea. D\u00e9jelo en blanco para utilizar un carpeta interna del programa.", + "LabelChannelDownloadAge": "Borrar contenido despues de: (d\u00edas)", + "LabelChannelDownloadAgeHelp": "Todo contenido descargado anterior se borrar\u00e1. Continuar\u00e1 estando disponible v\u00eda streaming de internet.", + "ChannelSettingsFormHelp": "Instale canales como Trailers y Vimeo desde el cat\u00e1logo de plugins.", + "LabelSelectCollection": "Seleccionar colecci\u00f3n:", + "ViewTypeMovies": "Pel\u00edculas", + "ViewTypeTvShows": "TV", + "ViewTypeGames": "Juegos", + "ViewTypeMusic": "M\u00fasica", + "ViewTypeBoxSets": "Colecciones", + "ViewTypeChannels": "Canales", + "ViewTypeLiveTV": "Tv en vivo", + "HeaderOtherDisplaySettings": "Configuraci\u00f3n de pantalla", + "HeaderMyViews": "Mis vistas", + "LabelSelectFolderGroups": "Agrupar autom\u00e1ticamente las siguientes carpetas en vistas tales como pel\u00edculas, m\u00fasica y televisi\u00f3n", + "LabelSelectFolderGroupsHelp": "Las carpetas que no est\u00e9n marcadas se mostrar\u00e1n por s\u00ed mismas en su propia secci\u00f3n.", + "OptionDisplayAdultContent": "Mostrar contenido para adultos", + "OptionLibraryFolders": "Vista de carpeta", + "TitleRemoteControl": "Control remoto", + "OptionLatestTvRecordings": "\u00daltimas grabaciones", + "LabelProtocolInfo": "Informaci\u00f3n de protocolo:", + "LabelProtocolInfoHelp": "El valor que se utilizar\u00e1 cuando se responde a una solicitud GetProtocolInfo desde el dispositivo.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser incluye soporte nativo para XBMC, Nfo, metadatos e im\u00e1genes. Para activar o desactivar los metadatos XBMC, utilice la ficha Avanzadas para configurar opciones para sus tipos de medios.", + "LabelXbmcMetadataUser": "A\u00f1adir datos de reproducciones de usuario a los nfo\u00b4s para:", + "LabelXbmcMetadataUserHelp": "Activar esto para mantener sincronizados los datos de reproducci\u00f3n entre Media Browser y Xbmc.", + "LabelXbmcMetadataDateFormat": "Formato de fecha de estreno:", + "LabelXbmcMetadataDateFormatHelp": "Todas las fechas dentro de los nfo se leer\u00e1n y se escribir\u00e1n usando este formato.", + "LabelXbmcMetadataSaveImagePaths": "Grabar las rutas de las im\u00e1genes en los archivos nfo", + "LabelXbmcMetadataSaveImagePathsHelp": "\nEsto se recomienda si usted tiene los nombres de archivo de imagen que no se ajusten a las directrices de XBMC.", + "LabelXbmcMetadataEnablePathSubstitution": "Habilitar rutas de sustituci\u00f3n", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Permite la sustituci\u00f3n de las rutas de im\u00e1genes utilizando la configuraci\u00f3n de rutas de sustituci\u00f3n en las opciones del servidor.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Ver rutas de sustituci\u00f3n.", + "LabelGroupChannelsIntoViews": "Visualice los siguientes canales dentro de mis vistas:", + "LabelGroupChannelsIntoViewsHelp": "Si est\u00e1 activado, estos canales se mostrar\u00e1n directamente junto a Mis Vistas. Si est\u00e1 desactivada, ser\u00e1n mostrados separadamente en la vista de Canales.", + "LabelDisplayCollectionsView": "Mostrar una vista Colecciones para mostrar colecciones de pel\u00edculas", + "LabelXbmcMetadataEnableExtraThumbs": "Copiar extrafanart en extrathumbs", + "LabelXbmcMetadataEnableExtraThumbsHelp": "Cuando se descargan im\u00e1genes se pueden guardar tanto en extrafanart como en extrathumbs para una m\u00e1xima compatibilidad con el skin de XBMC.", + "TabServices": "Servicios", + "TabLogs": "Logs", + "HeaderServerLogFiles": "Archivos de log del servidor:", + "TabBranding": "Branding", + "HeaderBrandingHelp": "Personalizar la apariencia de Explorador de medios para satisfacer las necesidades de su grupo u organizaci\u00f3n.", + "LabelLoginDisclaimer": "Login renuncia:", + "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", + "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", + "LabelAutomaticallyDonateHelp": "Usted puede cancelar en cualquier momento desde su cuenta de PayPal." +} \ 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 d09fdedee..c8f3bcb05 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -837,14 +837,14 @@ "LabelGroupChannelsIntoViewsHelp": "Al habilitarse, estos canales ser\u00e1n desplegados directamente junto con otras vistas. Si permanecen deshabilitados, ser\u00e1n desplegados dentro de una vista independiente de Canales.", "LabelDisplayCollectionsView": "Desplegar una Vista de colecciones para mostrar las colecciones de pel\u00edculas", "LabelXbmcMetadataEnableExtraThumbs": "Copiar extrafanart en extrathumbs", - "LabelXbmcMetadataEnableExtraThumbsHelp": "Cuando se descargan las im\u00e1genes \u00e9stas pueden ser almacenadas en extrafanart y extrathumbs para una m\u00e1xima compatibilidad con los skins de Xbmc.", + "LabelXbmcMetadataEnableExtraThumbsHelp": "Cuando se descargan im\u00e1genes \u00e9stas pueden ser almacenadas en extrafanart y extrathumbs para una m\u00e1xima compatibilidad con los skins de Xbmc.", "TabServices": "Servicios", "TabLogs": "Registros", "HeaderServerLogFiles": "Archivos de registro del servidor:", - "TabBranding": "Branding", + "TabBranding": "Establecer Marca", "HeaderBrandingHelp": "Personaliza la apariencia de Media Browser para ajustarla a las necesidades de tu grupo u organizaci\u00f3n.", - "LabelLoginDisclaimer": "Aviso legal de Inicio de Sesi\u00f3n:", - "LabelLoginDisclaimerHelp": "Se mostrara al final de la pagina de inicio de sesi\u00f3n.", + "LabelLoginDisclaimer": "Aviso de Inicio de Sesi\u00f3n:", + "LabelLoginDisclaimerHelp": "Esto se mostrara al final de la pagina de inicio de sesi\u00f3n.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", "LabelAutomaticallyDonateHelp": "Puedes cancelarlo en cualquier momento por medio de tu cuenta PayPal." } \ 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 13e385da3..591fe98b9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -837,14 +837,14 @@ "LabelGroupChannelsIntoViewsHelp": "Se abilitata, questi canali verranno visualizzati direttamente accanto ad altri punti di vista. Se disattivato, saranno visualizzati all'interno di una visione canali separati.", "LabelDisplayCollectionsView": "Visualizzare una vista Collezioni per mostrare collezioni di film", "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", + "LabelXbmcMetadataEnableExtraThumbsHelp": "Quando si scaricano le immagini possono essere salvate in entrambi extrafanart e extrathumbs per la massima compatibilit\u00e0 cutanea XBMC.", + "TabServices": "Servizi", "TabLogs": "Logs", "HeaderServerLogFiles": "Server log files:", "TabBranding": "Branding", - "HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.", + "HeaderBrandingHelp": "Personalizzare l'aspetto del browser media per soddisfare le esigenze del vostro gruppo o organizzazione.", "LabelLoginDisclaimer": "Login disclaimer:", - "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", - "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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." } \ 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 85066b4fb..b4d49e5b3 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -253,7 +253,7 @@ "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", + "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.", @@ -265,13 +265,13 @@ "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", + "TabGames": "Gry", + "TabMusic": "Muzyka", + "TabOthers": "Inne", "HeaderExtractChapterImagesFor": "Extract chapter images for:", - "OptionMovies": "Movies", - "OptionEpisodes": "Episodes", - "OptionOtherVideos": "Other Videos", + "OptionMovies": "Filmy", + "OptionEpisodes": "Odcinki", + "OptionOtherVideos": "Inne widea", "TitleMetadata": "Metadata", "LabelAutomaticUpdatesFanart": "Enable automatic updates from FanArt.tv", "LabelAutomaticUpdatesTmdb": "Enable automatic updates from TheMovieDB.org", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json index 1716dc66a..96c3429c9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -837,14 +837,14 @@ "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.", "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", + "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.", "TabServices": "Servi\u00e7os", "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.", - "LabelLoginDisclaimer": "Login disclaimer:", - "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", - "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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 m\u00eas", + "LabelAutomaticallyDonateHelp": "Voc\u00ea pode cancelar a qualquer hora atrav\u00e9s de sua conta do PayPal." } \ 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 5e00322d8..c1150bc2b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -284,7 +284,7 @@ "ButtonAutoScroll": "\u0410\u0432\u0442\u043e\u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430", "LabelImageSavingConvention": "\u0424\u043e\u0440\u043c\u0430\u0442 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432:", "LabelImageSavingConventionHelp": "\u0412 Media Browser \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u044e\u0442\u0441\u044f \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0438\u043c\u0435\u044e\u0449\u0438\u0435\u0441\u044f \u0432 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445 \u043c\u0435\u0434\u0438\u0430\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445. \u0412\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432.", - "OptionImageSavingCompatible": "\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0439 - Media Browse\/XBMCr\/Plex", + "OptionImageSavingCompatible": "\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0439 - Media Browser\/XBMC\/Plex", "OptionImageSavingStandard": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 - MB2", "ButtonSignIn": "\u0412\u043e\u0439\u0442\u0438", "TitleSignIn": "\u0412\u0445\u043e\u0434", @@ -427,7 +427,7 @@ "OptionUpscalingHelp": "\u0412 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0451\u0442 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u0438\u0434\u0435\u043e, \u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u0441\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440.", "EditCollectionItemsHelp": "\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u0438\u043b\u0438 \u0443\u0431\u0438\u0440\u0430\u0439\u0442\u0435 \u043b\u044e\u0431\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b, \u0441\u0435\u0440\u0438\u0430\u043b\u044b, \u0430\u043b\u044c\u0431\u043e\u043c\u044b, \u043a\u043d\u0438\u0433\u0438 \u0438\u043b\u0438 \u0438\u0433\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e.", "HeaderAddTitles": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u044f", - "LabelEnableDlnaPlayTo": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 DLNA", + "LabelEnableDlnaPlayTo": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DLNA-\u0440\u0435\u0436\u0438\u043c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445", "LabelEnableDlnaPlayToHelp": "Media Browser \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u0441\u0435\u0442\u0438 \u0438 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043c\u0438.", "LabelEnableDlnaDebugLogging": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 DLNA \u0432 \u0416\u0443\u0440\u043d\u0430\u043b\u0435", "LabelEnableDlnaDebugLoggingHelp": "\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0416\u0443\u0440\u043d\u0430\u043b\u0430 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043e\u0431\u044a\u0451\u043c\u0430, \u0430 \u044d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u0438\u043b\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043f\u043e\u043b\u0430\u0434\u043e\u043a.", @@ -553,15 +553,15 @@ "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.", "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\u0434\u0435\u043d\u0438\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435", - "LabelEnableDlnaServer": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DLNA \u0441\u0435\u0440\u0432\u0435\u0440", - "LabelEnableDlnaServerHelp": "\u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 UPnP \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0432 \u0441\u0435\u0442\u0438 \u043e\u0431\u0437\u043e\u0440 \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f Media Browser.", + "LabelEnableDlnaServer": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DLNA-\u0441\u0435\u0440\u0432\u0435\u0440", + "LabelEnableDlnaServerHelp": "\u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 UPnP-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0432 \u0441\u0435\u0442\u0438 \u043e\u0431\u0437\u043e\u0440 \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f Media Browser.", "LabelEnableBlastAliveMessages": "\u0423\u0447\u0430\u0449\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438", "LabelEnableBlastAliveMessagesHelp": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0434\u0451\u0436\u043d\u043e \u0434\u0440\u0443\u0433\u0438\u043c\u0438 UPnP \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0432 \u0441\u0435\u0442\u0438.", "LabelBlastMessageInterval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438, \u0441", "LabelBlastMessageIntervalHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", "LabelDefaultUser": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", "LabelDefaultUserHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u0447\u044c\u044f \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445. \u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439.", - "TitleDlna": "DLNA \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "TitleDlna": "DLNA-\u0440\u0435\u0436\u0438\u043c", "TitleChannels": "\u041a\u0430\u043d\u0430\u043b\u044b", "HeaderServerSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0430", "LabelWeatherDisplayLocation": "\u041f\u043e\u0433\u043e\u0434\u0430 \u0434\u043b\u044f \u043c\u0435\u0441\u0442\u043d\u043e\u0441\u0442\u0438:", @@ -822,14 +822,14 @@ "OptionLatestTvRecordings": "\u041d\u043e\u0432\u0438\u043d\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439", "LabelProtocolInfo": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0435:", "LabelProtocolInfoHelp": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u0435 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u044b GetProtocolInfo \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", - "TabXbmcMetadata": "Xbmc", - "HeaderXbmcMetadataHelp": "Media Browser \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0434\u043b\u044f Xbmc Nfo. \u0414\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 Xbmc, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0442\u0438\u043f\u0430\u043c \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u0439.", - "LabelXbmcMetadataUser": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 nfo \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043b\u044f:", - "LabelXbmcMetadataUserHelp": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 Media Browser \u0438 Xbmc \u0445\u0440\u0430\u043d\u0438\u043c\u044b\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430.", + "TabXbmcMetadata": "XBMC", + "HeaderXbmcMetadataHelp": "Media Browser \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 XBMC \u0434\u043b\u044f NFO \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432. \u0414\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 XBMC, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0421\u043b\u0443\u0436\u0431\u044b, \u0447\u0442\u043e\u0431\u044b \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0442\u0438\u043f\u0430\u043c \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u0439.", + "LabelXbmcMetadataUser": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 NFO \u0444\u0430\u0439\u043b\u044b \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043b\u044f:", + "LabelXbmcMetadataUserHelp": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u043c\u044b\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043c\u0435\u0436\u0434\u0443 Media Browser \u0438 XBMC.", "LabelXbmcMetadataDateFormat": "\u0424\u043e\u0440\u043c\u0430\u0442 \u0434\u0430\u0442\u044b \u0432\u044b\u043f\u0443\u0441\u043a\u0430:", - "LabelXbmcMetadataDateFormatHelp": "\u0412\u0441\u0435 \u0434\u0430\u0442\u044b \u0432 nfo \u0431\u0443\u0434\u0443\u0442 \u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430.", - "LabelXbmcMetadataSaveImagePaths": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0443\u0442\u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432 nfo \u0444\u0430\u0439\u043b\u0430\u0445", - "LabelXbmcMetadataSaveImagePathsHelp": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u043d\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u044f\u0449\u0438\u043c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c Xbmc.", + "LabelXbmcMetadataDateFormatHelp": "\u0412\u0441\u0435 \u0434\u0430\u0442\u044b \u0432 NFO \u0444\u0430\u0439\u043b\u0430\u0445 \u0431\u0443\u0434\u0443\u0442 \u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430.", + "LabelXbmcMetadataSaveImagePaths": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0443\u0442\u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432 NFO \u0444\u0430\u0439\u043b\u0430\u0445", + "LabelXbmcMetadataSaveImagePathsHelp": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u043d\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u044f\u0449\u0438\u043c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c XBMC.", "LabelXbmcMetadataEnablePathSubstitution": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439", "LabelXbmcMetadataEnablePathSubstitutionHelp": "\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439 \u043a \u0440\u0438\u0441\u0443\u043d\u043a\u0430\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a \u043f\u0443\u0442\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.", "LabelXbmcMetadataEnablePathSubstitutionHelp2": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439.", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index 028689093..c2e49294f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -838,13 +838,13 @@ "LabelDisplayCollectionsView": "Visa en Samlingar-vy f\u00f6r filmsamlingar", "LabelXbmcMetadataEnableExtraThumbs": "Kopiera extrafanart till extrathumbs", "LabelXbmcMetadataEnableExtraThumbsHelp": "N\u00e4r bilder h\u00e4mtas fr\u00e5n Internet kan de sparas i b\u00e5de extrafanart- och extrathumbs-mapparna f\u00f6r att ge maximal kompatibilitet med Xbmc-skins.", - "TabServices": "Services", - "TabLogs": "Logs", - "HeaderServerLogFiles": "Server log files:", + "TabServices": "Tj\u00e4nster", + "TabLogs": "Loggfiler", + "HeaderServerLogFiles": "Serverloggfiler:", "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 each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "HeaderBrandingHelp": "Anpassa utseendet p\u00e5 Media Browser till din grupp eller f\u00f6retags \u00f6nskem\u00e5l.", + "LabelLoginDisclaimer": "Ansvarsbegr\u00e4nsning vid inloggning:", + "LabelLoginDisclaimerHelp": "Detta visas l\u00e4ngst ned p\u00e5 inloggningssidan.", + "LabelAutomaticallyDonate": "Donera automatiskt detta belopp varje m\u00e5nad", + "LabelAutomaticallyDonateHelp": "Du kan avbryta n\u00e4r som helst via ditt PayPal-konto." } \ 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 859011f6e..a3dea9e47 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -354,6 +354,8 @@ + + diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs index b8566ee51..206a04460 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg // Windows builds: http://ffmpeg.zeranoe.com/builds/ // Linux builds: http://ffmpeg.gusari.org/static/ // OS X builds: http://ffmpegmac.net/ + // OS X x64: http://www.evermeet.cx/ffmpeg/ public static string Version = ffmpegOsType("Version"); @@ -113,15 +114,23 @@ namespace MediaBrowser.ServerApplication.FFMpeg return new[] { "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140612-git-3a1c895-win32-static.7z", - "https://www.dropbox.com/s/lllit55bynbz6zc/ffmpeg-20140612-git-3a1c895-win32-static.7z?dl=1" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140612-git-3a1c895-win32-static.7z" }; case PlatformID.Unix: + if (PlatformDetection.IsMac && PlatformDetection.IsX86) + { + return new[] + { + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/osx/ffmpeg-osx-20131121.gz" + }; + } + if (PlatformDetection.IsMac && PlatformDetection.IsX86_64) { return new[] { - "https://www.dropbox.com/s/n188rxbulqem8ry/ffmpeg-osx-20131121.gz?dl=1" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/osx/ffprobe-x64-2.2.4.7z" }; } @@ -132,7 +141,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg return new[] { "http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.latest.tar.gz", - "https://www.dropbox.com/s/k9s02pv5to6slfb/ffmpeg.static.32bit.2014-05-06.tar.gz?dl=1" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg.static.32bit.2014-05-06.tar.gz" }; } @@ -141,13 +150,13 @@ namespace MediaBrowser.ServerApplication.FFMpeg return new[] { "http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.latest.tar.gz", - "https://www.dropbox.com/s/onuregwghywnzjo/ffmpeg.static.64bit.2014-05-05.tar.gz?dl=1" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg.static.64bit.2014-05-05.tar.gz" }; } } - //No Unix version available + // No Unix version available return new string[] { }; default: diff --git a/MediaBrowser.Tests/MediaBrowser.Tests.csproj b/MediaBrowser.Tests/MediaBrowser.Tests.csproj index d31615952..95ae25f93 100644 --- a/MediaBrowser.Tests/MediaBrowser.Tests.csproj +++ b/MediaBrowser.Tests/MediaBrowser.Tests.csproj @@ -80,6 +80,10 @@ {2E781478-814D-4A48-9D80-BFF206441A65} MediaBrowser.Server.Implementations + + {23499896-b135-4527-8574-c26e926ea99e} + MediaBrowser.XbmcMetadata + diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index a3928d774..972595b94 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -56,13 +56,13 @@ - - - - - - - + + + + + + + diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 49851f42a..6e31b0178 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; +using MediaBrowser.XbmcMetadata.Savers; namespace MediaBrowser.XbmcMetadata.Parsers { @@ -63,8 +64,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers ValidationType = ValidationType.None }; - //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken); - Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken); + Fetch(item, metadataFile, settings, cancellationToken); } /// @@ -73,11 +73,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The item. /// The metadata file. /// The settings. - /// The encoding. /// The cancellation token. - private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) + private void Fetch(T item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(metadataFile)) + using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs new file mode 100644 index 000000000..800cb8e10 --- /dev/null +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -0,0 +1,112 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml; + +namespace MediaBrowser.XbmcMetadata.Savers +{ + public class AlbumNfoSaver : BaseNfoSaver + { + public AlbumNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + { + } + + public override string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "album.nfo"); + } + + protected override string GetRootElementName(IHasMetadata item) + { + return "album"; + } + + public override bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is MusicAlbum && updateType >= ItemUpdateType.MetadataDownload; + } + + protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer) + { + var album = (MusicAlbum)item; + + var tracks = album.Tracks + .ToList(); + + var artists = tracks + .SelectMany(i => + { + var list = new List(); + + if (!string.IsNullOrEmpty(i.AlbumArtist)) + { + list.Add(i.AlbumArtist); + } + list.AddRange(i.Artists); + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase); + + foreach (var artist in artists) + { + writer.WriteElementString("artist", artist); + } + + AddTracks(tracks, writer); + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + private void AddTracks(IEnumerable - /// The path. - /// The XML tags used. - /// System.String. - private static string GetCustomTags(string path, List xmlTagsUsed) - { - var settings = new XmlReaderSettings - { - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true, - ValidationType = ValidationType.None - }; - - var builder = new StringBuilder(); - - using (var streamReader = new StreamReader(path, Encoding.UTF8)) - { - // Use XmlReader for best performance - using (var reader = XmlReader.Create(streamReader, settings)) - { - reader.MoveToContent(); - - // Loop through each element - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - var name = reader.Name; - - if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - builder.AppendLine(reader.ReadOuterXml()); - } - else - { - reader.Skip(); - } - } - } - } - } - - return builder.ToString(); - } - - public static void AddMediaInfo(T item, StringBuilder builder) - where T : BaseItem, IHasMediaSources - { - builder.Append(""); - builder.Append(""); - - foreach (var stream in item.GetMediaSources(false).First().MediaStreams) - { - builder.Append("<" + stream.Type.ToString().ToLower() + ">"); - - if (!string.IsNullOrEmpty(stream.Codec)) - { - builder.Append("" + SecurityElement.Escape(stream.Codec) + ""); - builder.Append("" + SecurityElement.Escape(stream.Codec) + ""); - } - - if (stream.BitRate.HasValue) - { - builder.Append("" + stream.BitRate.Value.ToString(UsCulture) + ""); - } - - if (stream.Width.HasValue) - { - builder.Append("" + stream.Width.Value.ToString(UsCulture) + ""); - } - - if (stream.Height.HasValue) - { - builder.Append("" + stream.Height.Value.ToString(UsCulture) + ""); - } - - if (!string.IsNullOrEmpty(stream.AspectRatio)) - { - builder.Append("" + SecurityElement.Escape(stream.AspectRatio) + ""); - builder.Append("" + SecurityElement.Escape(stream.AspectRatio) + ""); - } - - var framerate = stream.AverageFrameRate ?? stream.RealFrameRate; - - if (framerate.HasValue) - { - builder.Append("" + framerate.Value.ToString(UsCulture) + ""); - } - - if (!string.IsNullOrEmpty(stream.Language)) - { - builder.Append("" + SecurityElement.Escape(stream.Language) + ""); - } - - var scanType = stream.IsInterlaced ? "interlaced" : "progressive"; - if (!string.IsNullOrEmpty(scanType)) - { - builder.Append("" + SecurityElement.Escape(scanType) + ""); - } - - if (stream.Channels.HasValue) - { - builder.Append("" + stream.Channels.Value.ToString(UsCulture) + ""); - } - - if (stream.SampleRate.HasValue) - { - builder.Append("" + stream.SampleRate.Value.ToString(UsCulture) + ""); - } - - builder.Append("" + SecurityElement.Escape(stream.IsDefault.ToString()) + ""); - builder.Append("" + SecurityElement.Escape(stream.IsForced.ToString()) + ""); - - if (stream.Type == MediaStreamType.Video) - { - if (item.RunTimeTicks.HasValue) - { - var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value); - - builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); - builder.Append("" + Convert.ToInt32(timespan.TotalSeconds).ToString(UsCulture) + ""); - } - - var video = item as Video; - - if (video != null) - { - //AddChapters(video, builder, itemRepository); - - if (video.Video3DFormat.HasValue) - { - switch (video.Video3DFormat.Value) - { - case Video3DFormat.FullSideBySide: - builder.Append("FSBS"); - break; - case Video3DFormat.FullTopAndBottom: - builder.Append("FTAB"); - break; - case Video3DFormat.HalfSideBySide: - builder.Append("HSBS"); - break; - case Video3DFormat.HalfTopAndBottom: - builder.Append("HTAB"); - break; - } - } - } - } - - builder.Append(""); - } - - builder.Append(""); - builder.Append(""); - } - - /// - /// Adds the common nodes. - /// - /// Task. - public static void AddCommonNodes(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config) - { - var overview = (item.Overview ?? string.Empty) - .StripHtml() - .Replace(""", "'"); - - var options = config.GetNfoConfiguration(); - - if (item is MusicArtist) - { - builder.Append(""); - } - else if (item is MusicAlbum) - { - builder.Append(""); - } - else - { - builder.Append(""); - } - - var hasShortOverview = item as IHasShortOverview; - if (hasShortOverview != null) - { - var outline = (hasShortOverview.ShortOverview ?? string.Empty) - .StripHtml() - .Replace(""", "'"); - - builder.Append(""); - } - else - { - builder.Append(""); - } - - builder.Append("" + SecurityElement.Escape(item.CustomRating ?? string.Empty) + ""); - builder.Append("" + item.IsLocked.ToString().ToLower() + ""); - - if (item.LockedFields.Count > 0) - { - builder.Append("" + string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()) + ""); - } - - if (!string.IsNullOrEmpty(item.DisplayMediaType)) - { - builder.Append("" + SecurityElement.Escape(item.DisplayMediaType) + ""); - } - - builder.Append("" + SecurityElement.Escape(item.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")) + ""); - - builder.Append("" + SecurityElement.Escape(item.Name ?? string.Empty) + ""); - builder.Append("" + SecurityElement.Escape(item.Name ?? string.Empty) + ""); - - var directors = item.People - .Where(i => IsPersonType(i, PersonType.Director)) - .Select(i => i.Name) - .ToList(); - - foreach (var person in directors) - { - builder.Append("" + SecurityElement.Escape(person) + ""); - } - - var writers = item.People - .Where(i => IsPersonType(i, PersonType.Writer)) - .Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - foreach (var person in writers) - { - builder.Append("" + SecurityElement.Escape(person) + ""); - } - - foreach (var person in writers) - { - builder.Append("" + SecurityElement.Escape(person) + ""); - } - - var hasTrailer = item as IHasTrailers; - if (hasTrailer != null) - { - foreach (var trailer in hasTrailer.RemoteTrailers) - { - builder.Append("" + SecurityElement.Escape(GetOutputTrailerUrl(trailer.Url)) + ""); - } - } - - if (item.CommunityRating.HasValue) - { - builder.Append("" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + ""); - } - - if (item.ProductionYear.HasValue) - { - builder.Append("" + SecurityElement.Escape(item.ProductionYear.Value.ToString(UsCulture)) + ""); - } - - if (!string.IsNullOrEmpty(item.ForcedSortName)) - { - builder.Append("" + SecurityElement.Escape(item.ForcedSortName) + ""); - } - - if (!string.IsNullOrEmpty(item.OfficialRating)) - { - builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); - } - - if (!string.IsNullOrEmpty(item.OfficialRatingDescription)) - { - builder.Append("" + SecurityElement.Escape(item.OfficialRatingDescription) + ""); - } - - var hasAspectRatio = item as IHasAspectRatio; - if (hasAspectRatio != null) - { - if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) - { - builder.Append("" + SecurityElement.Escape(hasAspectRatio.AspectRatio) + ""); - } - } - - if (!string.IsNullOrEmpty(item.HomePageUrl)) - { - builder.Append("" + SecurityElement.Escape(item.HomePageUrl) + ""); - } - - var rt = item.GetProviderId(MetadataProviders.RottenTomatoes); - - if (!string.IsNullOrEmpty(rt)) - { - builder.Append("" + SecurityElement.Escape(rt) + ""); - } - - var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); - - if (!string.IsNullOrEmpty(tmdbCollection)) - { - builder.Append("" + SecurityElement.Escape(tmdbCollection) + ""); - } - - var imdb = item.GetProviderId(MetadataProviders.Imdb); - if (!string.IsNullOrEmpty(imdb)) - { - if (item is Series) - { - builder.Append("" + SecurityElement.Escape(imdb) + ""); - } - else - { - builder.Append("" + SecurityElement.Escape(imdb) + ""); - } - } - - // Series xml saver already saves this - if (!(item is Series)) - { - var tvdb = item.GetProviderId(MetadataProviders.Tvdb); - if (!string.IsNullOrEmpty(tvdb)) - { - builder.Append("" + SecurityElement.Escape(tvdb) + ""); - } - } - - var tmdb = item.GetProviderId(MetadataProviders.Tmdb); - if (!string.IsNullOrEmpty(tmdb)) - { - builder.Append("" + SecurityElement.Escape(tmdb) + ""); - } - - var tvcom = item.GetProviderId(MetadataProviders.Tvcom); - if (!string.IsNullOrEmpty(tvcom)) - { - builder.Append("" + SecurityElement.Escape(tvcom) + ""); - } - - var hasLanguage = item as IHasPreferredMetadataLanguage; - if (hasLanguage != null) - { - if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage)) - { - builder.Append("" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + ""); - } - } - - if (item.PremiereDate.HasValue && !(item is Episode)) - { - var formatString = options.ReleaseDateFormat; - - if (item is MusicArtist) - { - builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); - } - else - { - builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); - builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); - } - } - - if (item.EndDate.HasValue) - { - if (!(item is Episode)) - { - var formatString = options.ReleaseDateFormat; - - builder.Append("" + SecurityElement.Escape(item.EndDate.Value.ToString(formatString)) + ""); - } - } - - var hasCriticRating = item as IHasCriticRating; - - if (hasCriticRating != null) - { - if (hasCriticRating.CriticRating.HasValue) - { - builder.Append("" + SecurityElement.Escape(hasCriticRating.CriticRating.Value.ToString(UsCulture)) + ""); - } - - if (!string.IsNullOrEmpty(hasCriticRating.CriticRatingSummary)) - { - builder.Append(""); - } - } - - var hasDisplayOrder = item as IHasDisplayOrder; - - if (hasDisplayOrder != null) - { - if (!string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) - { - builder.Append("" + SecurityElement.Escape(hasDisplayOrder.DisplayOrder) + ""); - } - } - - if (item.VoteCount.HasValue) - { - builder.Append("" + SecurityElement.Escape(item.VoteCount.Value.ToString(UsCulture)) + ""); - } - - var hasBudget = item as IHasBudget; - if (hasBudget != null) - { - if (hasBudget.Budget.HasValue) - { - builder.Append("" + SecurityElement.Escape(hasBudget.Budget.Value.ToString(UsCulture)) + ""); - } - - if (hasBudget.Revenue.HasValue) - { - builder.Append("" + SecurityElement.Escape(hasBudget.Revenue.Value.ToString(UsCulture)) + ""); - } - } - - var hasMetascore = item as IHasMetascore; - if (hasMetascore != null && hasMetascore.Metascore.HasValue) - { - builder.Append("" + SecurityElement.Escape(hasMetascore.Metascore.Value.ToString(UsCulture)) + ""); - } - - // Use original runtime here, actual file runtime later in MediaInfo - var runTimeTicks = item.RunTimeTicks; - - if (runTimeTicks.HasValue) - { - var timespan = TimeSpan.FromTicks(runTimeTicks.Value); - - builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); - } - - var hasTaglines = item as IHasTaglines; - if (hasTaglines != null) - { - foreach (var tagline in hasTaglines.Taglines) - { - builder.Append("" + SecurityElement.Escape(tagline) + ""); - } - } - - var hasProductionLocations = item as IHasProductionLocations; - if (hasProductionLocations != null) - { - foreach (var country in hasProductionLocations.ProductionLocations) - { - builder.Append("" + SecurityElement.Escape(country) + ""); - } - } - - foreach (var genre in item.Genres) - { - builder.Append("" + SecurityElement.Escape(genre) + ""); - } - - foreach (var studio in item.Studios) - { - builder.Append("" + SecurityElement.Escape(studio) + ""); - } - - var hasTags = item as IHasTags; - if (hasTags != null) - { - foreach (var tag in hasTags.Tags) - { - if (item is MusicAlbum || item is MusicArtist) - { - builder.Append(""); - } - else - { - builder.Append("" + SecurityElement.Escape(tag) + ""); - } - } - } - - var hasKeywords = item as IHasKeywords; - if (hasKeywords != null) - { - foreach (var tag in hasKeywords.Keywords) - { - builder.Append("" + SecurityElement.Escape(tag) + ""); - } - } - - var hasAwards = item as IHasAwards; - if (hasAwards != null && !string.IsNullOrEmpty(hasAwards.AwardSummary)) - { - builder.Append("" + SecurityElement.Escape(hasAwards.AwardSummary) + ""); - } - - var externalId = item.GetProviderId(MetadataProviders.AudioDbArtist); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.Zap2It); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.Gamesdb); - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - externalId = item.GetProviderId(MetadataProviders.TvRage); - if (!string.IsNullOrEmpty(externalId)) - { - builder.Append("" + SecurityElement.Escape(externalId) + ""); - } - - if (options.SaveImagePathsInNfo) - { - AddImages(item, builder, fileSystem, config); - } - - AddUserData(item, builder, userManager, userDataRepo, options); - - AddActors(item, builder, libraryManager, fileSystem, config); - - var folder = item as BoxSet; - if (folder != null) - { - AddCollectionItems(folder, builder); - } - } - - public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository) - { - var chapters = repository.GetChapters(item.Id); - - foreach (var chapter in chapters) - { - builder.Append(""); - builder.Append("" + SecurityElement.Escape(chapter.Name) + ""); - - var time = TimeSpan.FromTicks(chapter.StartPositionTicks); - var ms = Convert.ToInt64(time.TotalMilliseconds); - - builder.Append("" + SecurityElement.Escape(ms.ToString(UsCulture)) + ""); - builder.Append(""); - } - } - - public static void AddCollectionItems(Folder item, StringBuilder builder) - { - var items = item.LinkedChildren - .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) - .ToList(); - - foreach (var link in items) - { - builder.Append(""); - - builder.Append("" + SecurityElement.Escape(link.ItemName) + ""); - builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); - - if (link.ItemYear.HasValue) - { - builder.Append("" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + ""); - } - - builder.Append(""); - } - } - - /// - /// Gets the output trailer URL. - /// - /// The URL. - /// System.String. - private static string GetOutputTrailerUrl(string url) - { - // This is what xbmc expects - - return url.Replace("http://www.youtube.com/watch?v=", - "plugin://plugin.video.youtube/?action=play_video&videoid=", - StringComparison.OrdinalIgnoreCase); - } - - private static void AddImages(BaseItem item, StringBuilder builder, IFileSystem fileSystem, IServerConfigurationManager config) - { - builder.Append(""); - - var poster = item.PrimaryImagePath; - - if (!string.IsNullOrEmpty(poster)) - { - builder.Append("" + SecurityElement.Escape(GetPathToSave(item.PrimaryImagePath, fileSystem, config)) + ""); - } - - foreach (var backdrop in item.GetImages(ImageType.Backdrop)) - { - builder.Append("" + SecurityElement.Escape(GetPathToSave(backdrop.Path, fileSystem, config)) + ""); - } - - builder.Append(""); - } - - private static void AddUserData(BaseItem item, StringBuilder builder, IUserManager userManager, IUserDataManager userDataRepo, XbmcMetadataOptions options) - { - var userId = options.UserId; - if (string.IsNullOrWhiteSpace(userId)) - { - return; - } - - var user = userManager.GetUserById(new Guid(userId)); - - if (user == null) - { - return; - } - - if (item.IsFolder) - { - return; - } - - var userdata = userDataRepo.GetUserData(user.Id, item.GetUserDataKey()); - - builder.Append("" + userdata.PlayCount.ToString(UsCulture) + ""); - builder.Append("" + userdata.Played.ToString().ToLower() + ""); - - if (userdata.LastPlayedDate.HasValue) - { - builder.Append("" + SecurityElement.Escape(userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss")) + ""); - } - - builder.Append(""); - - var runTimeTicks = item.RunTimeTicks ?? 0; - - builder.Append("" + TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds.ToString(UsCulture) + ""); - builder.Append("" + TimeSpan.FromTicks(runTimeTicks).TotalSeconds.ToString(UsCulture) + ""); - - builder.Append(""); - } - - public static void AddActors(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config) - { - var actors = item.People - .Where(i => !IsPersonType(i, PersonType.Director) && !IsPersonType(i, PersonType.Writer)) - .ToList(); - - foreach (var person in actors) - { - builder.Append(""); - builder.Append("" + SecurityElement.Escape(person.Name ?? string.Empty) + ""); - builder.Append("" + SecurityElement.Escape(person.Role ?? string.Empty) + ""); - builder.Append("" + SecurityElement.Escape(person.Type ?? string.Empty) + ""); - - if (person.SortOrder.HasValue) - { - builder.Append("" + SecurityElement.Escape(person.SortOrder.Value.ToString(UsCulture)) + ""); - } - - try - { - var personEntity = libraryManager.GetPerson(person.Name); - - if (!string.IsNullOrEmpty(personEntity.PrimaryImagePath)) - { - builder.Append("" + SecurityElement.Escape(GetPathToSave(personEntity.PrimaryImagePath, fileSystem, config)) + ""); - } - } - catch (Exception) - { - // Already logged in core - } - - builder.Append(""); - } - } - - private static bool IsPersonType(PersonInfo person, string type) - { - return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); - } - - private static string GetPathToSave(string path, IFileSystem fileSystem, IServerConfigurationManager config) - { - foreach (var map in config.Configuration.PathSubstitutions) - { - path = fileSystem.SubstitutePath(path, map.From, map.To); - } - - return path; - } - - public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison) - { - var sb = new StringBuilder(); - - int previousIndex = 0; - int index = str.IndexOf(oldValue, comparison); - while (index != -1) - { - sb.Append(str.Substring(previousIndex, index - previousIndex)); - sb.Append(newValue); - index += oldValue.Length; - - previousIndex = index; - index = str.IndexOf(oldValue, index, comparison); - } - sb.Append(str.Substring(previousIndex)); - - return sb.ToString(); - } - } -} -- cgit v1.2.3 From 59de5c0d14fbf0c09926e37dce0c2e6de69000dd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Jul 2014 00:27:46 -0400 Subject: update translations --- MediaBrowser.Api/ConfigurationService.cs | 53 +++++++++++++++++++- MediaBrowser.Api/UserService.cs | 10 +++- .../Configuration/IServerConfigurationManager.cs | 6 +++ MediaBrowser.Controller/Entities/BaseItem.cs | 5 ++ MediaBrowser.Controller/Session/ISessionManager.cs | 2 +- MediaBrowser.Model/Configuration/ChapterOptions.cs | 4 -- .../Configuration/SubtitleOptions.cs | 1 + .../MediaInfo/FFProbeVideoInfo.cs | 2 +- .../Subtitles/OpenSubtitleDownloader.cs | 24 +++++++++ MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs | 10 ++-- .../Channels/ChannelManager.cs | 15 ++++-- .../Configuration/ServerConfigurationManager.cs | 57 +++++++++++++++++++++- .../HttpServer/Security/AuthService.cs | 2 +- .../Localization/JavaScript/ar.json | 17 ++++++- .../Localization/JavaScript/ca.json | 17 ++++++- .../Localization/JavaScript/cs.json | 17 ++++++- .../Localization/JavaScript/da.json | 17 ++++++- .../Localization/JavaScript/de.json | 21 ++++++-- .../Localization/JavaScript/el.json | 17 ++++++- .../Localization/JavaScript/en_GB.json | 17 ++++++- .../Localization/JavaScript/en_US.json | 17 ++++++- .../Localization/JavaScript/es_ES.json | 21 ++++++-- .../Localization/JavaScript/es_MX.json | 23 +++++++-- .../Localization/JavaScript/fr.json | 21 ++++++-- .../Localization/JavaScript/he.json | 17 ++++++- .../Localization/JavaScript/it.json | 21 ++++++-- .../Localization/JavaScript/kk.json | 21 ++++++-- .../Localization/JavaScript/ms.json | 17 ++++++- .../Localization/JavaScript/nb.json | 17 ++++++- .../Localization/JavaScript/nl.json | 21 ++++++-- .../Localization/JavaScript/pl.json | 17 ++++++- .../Localization/JavaScript/pt_BR.json | 21 ++++++-- .../Localization/JavaScript/pt_PT.json | 21 ++++++-- .../Localization/JavaScript/ru.json | 23 +++++++-- .../Localization/JavaScript/sv.json | 21 ++++++-- .../Localization/JavaScript/vi.json | 17 ++++++- .../Localization/JavaScript/zh_TW.json | 17 ++++++- .../Localization/Server/ar.json | 12 ++++- .../Localization/Server/ca.json | 12 ++++- .../Localization/Server/cs.json | 24 ++++++--- .../Localization/Server/da.json | 10 +++- .../Localization/Server/de.json | 18 +++++-- .../Localization/Server/el.json | 12 ++++- .../Localization/Server/en_GB.json | 12 ++++- .../Localization/Server/en_US.json | 12 ++++- .../Localization/Server/es_ES.json | 14 ++++-- .../Localization/Server/es_MX.json | 40 +++++++++------ .../Localization/Server/fr.json | 20 +++++--- .../Localization/Server/he.json | 16 ++++-- .../Localization/Server/it.json | 18 +++++-- .../Localization/Server/kk.json | 12 ++++- .../Localization/Server/ms.json | 12 ++++- .../Localization/Server/nb.json | 12 ++++- .../Localization/Server/nl.json | 14 ++++-- .../Localization/Server/pl.json | 12 ++++- .../Localization/Server/pt_BR.json | 10 +++- .../Localization/Server/pt_PT.json | 18 +++++-- .../Localization/Server/ru.json | 16 ++++-- .../Localization/Server/server.json | 13 ++++- .../Localization/Server/sv.json | 18 +++++-- .../Localization/Server/vi.json | 12 ++++- .../Localization/Server/zh_TW.json | 12 ++++- .../Session/SessionManager.cs | 13 +++-- MediaBrowser.ServerApplication/ApplicationHost.cs | 51 +------------------ MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- .../MediaBrowser.WebDashboard.csproj | 24 +++------ 66 files changed, 888 insertions(+), 232 deletions(-) (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 6710461ad..c83028bb2 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -1,6 +1,8 @@ -using MediaBrowser.Common.Extensions; +using System; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -65,6 +67,13 @@ namespace MediaBrowser.Api } + [Route("/System/Configuration/MetadataPlugins/Autoset", "POST")] + [Authenticated] + public class AutoSetMetadataOptions : IReturnVoid + { + + } + public class ConfigurationService : BaseApiService { /// @@ -79,13 +88,15 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; + private readonly ILibraryManager _libraryManager; - public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager) + public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager) { _jsonSerializer = jsonSerializer; _configurationManager = configurationManager; _fileSystem = fileSystem; _providerManager = providerManager; + _libraryManager = libraryManager; } /// @@ -111,6 +122,44 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + public void Post(AutoSetMetadataOptions request) + { + var service = AutoDetectMetadataService(); + + Logger.Info("Setting preferred metadata format to " + service); + + _configurationManager.SetPreferredMetadataService(service); + _configurationManager.SaveConfiguration(); + } + + private string AutoDetectMetadataService() + { + const string xbmc = "Xbmc Nfo"; + const string mb = "Media Browser Xml"; + + var paths = _libraryManager.GetDefaultVirtualFolders() + .SelectMany(i => i.Locations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => new DirectoryInfo(i)) + .ToList(); + + if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) + .SelectMany(i => i) + .Any()) + { + return xbmc; + } + + if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) + .SelectMany(i => i) + .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase))) + { + return mb; + } + + return xbmc; + } + /// /// Posts the specified configuraiton. /// diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index d5266038b..0edd013df 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -188,7 +188,11 @@ namespace MediaBrowser.Api public object Get(GetPublicUsers request) { - if (Request.IsLocal || !_config.Configuration.IsStartupWizardCompleted) + var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); + var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); + + if ((Request.IsLocal && isDashboard) || + !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers { @@ -196,7 +200,8 @@ namespace MediaBrowser.Api }); } - if (_sessionMananger.IsLocal(Request.RemoteIp)) + // TODO: Add or is authenticated + if (_sessionMananger.IsInLocalNetwork(Request.RemoteIp)) { return Get(new GetUsers { @@ -205,6 +210,7 @@ namespace MediaBrowser.Api }); } + // Return empty when external return ToOptimizedResult(new List()); } diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 6a2343a00..c3d1796f9 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -26,5 +26,11 @@ namespace MediaBrowser.Controller.Configuration /// /// The configuration. ServerConfiguration Configuration { get; } + + /// + /// Sets the preferred metadata service. + /// + /// The service. + void SetPreferredMetadataService(string service); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d89df5f12..b891bcfb2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1526,6 +1526,11 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsUnplayed(User user) { + if (user == null) + { + throw new ArgumentNullException("user"); + } + var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey()); return userdata == null || !userdata.Played; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4b30c964c..88d0e9c1a 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -281,6 +281,6 @@ namespace MediaBrowser.Controller.Session /// /// The remote endpoint. /// true if the specified remote endpoint is local; otherwise, false. - bool IsLocal(string remoteEndpoint); + bool IsInLocalNetwork(string remoteEndpoint); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs index 8a059a0a4..2e882ee61 100644 --- a/MediaBrowser.Model/Configuration/ChapterOptions.cs +++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs @@ -14,10 +14,6 @@ public ChapterOptions() { - EnableMovieChapterImageExtraction = true; - EnableEpisodeChapterImageExtraction = false; - EnableOtherVideoChapterImageExtraction = false; - DownloadMovieChapters = true; DisabledFetchers = new string[] { }; diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Configuration/SubtitleOptions.cs index 96e04e511..d50dba1b2 100644 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ b/MediaBrowser.Model/Configuration/SubtitleOptions.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Configuration public string OpenSubtitlesUsername { get; set; } public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } public SubtitleOptions() { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 57a40741f..d46ddf7dc 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -259,7 +259,7 @@ namespace MediaBrowser.Providers.MediaInfo { Chapters = chapters, Video = video, - ExtractImages = false, + ExtractImages = true, SaveChapters = false }, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs index 297f05867..c690fbd84 100644 --- a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs @@ -29,6 +29,15 @@ namespace MediaBrowser.Providers.Subtitles private readonly IServerConfigurationManager _config; private readonly IEncryptionManager _encryption; + private Timer _dailyTimer; + + // This is limited to 200 per day + private int _dailyDownloadCount; + + // It's 200 but this will be in-exact so buffer a little + // And the user may restart the server + private const int MaxDownloadsPerDay = 150; + public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption) { _logger = logManager.GetLogger(GetType().Name); @@ -37,6 +46,9 @@ namespace MediaBrowser.Providers.Subtitles _encryption = encryption; _config.ConfigurationUpdating += _config_ConfigurationUpdating; + + // Reset the count every 24 hours + _dailyTimer = new Timer(state => _dailyDownloadCount = 0, null, TimeSpan.FromHours(24), TimeSpan.FromHours(24)); } private const string PasswordHashPrefix = "h:"; @@ -100,6 +112,12 @@ namespace MediaBrowser.Providers.Subtitles throw new ArgumentNullException("id"); } + if (_dailyDownloadCount >= MaxDownloadsPerDay && + !_config.Configuration.SubtitleOptions.IsOpenSubtitleVipAccount) + { + throw new InvalidOperationException("Open Subtitle's daily download limit has been exceeded. Please try again tomorrow."); + } + var idParts = id.Split(new[] { '-' }, 3); var format = idParts[0]; @@ -272,6 +290,12 @@ namespace MediaBrowser.Providers.Subtitles public void Dispose() { _config.ConfigurationUpdating -= _config_ConfigurationUpdating; + + if (_dailyTimer != null) + { + _dailyTimer.Dispose(); + _dailyTimer = null; + } } } } diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs index ec02d3add..0546701f3 100644 --- a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs @@ -165,7 +165,7 @@ namespace MediaBrowser.Providers.TV { cancellationToken.ThrowIfCancellationRequested(); - result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.HasMetadata = result.Item != null; } @@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.TV return result; } - private async Task FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) + private async Task FetchMovieData(string tmdbId, string language, CancellationToken cancellationToken) { string dataFilePath = null; RootObject seriesInfo = null; @@ -198,12 +198,12 @@ namespace MediaBrowser.Providers.TV var item = new Series(); - ProcessMainInfo(item, preferredCountryCode, seriesInfo); + ProcessMainInfo(item, seriesInfo); return item; } - private void ProcessMainInfo(Series series, string countryCode, RootObject seriesInfo) + private void ProcessMainInfo(Series series, RootObject seriesInfo) { series.Name = seriesInfo.name; series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.id.ToString(_usCulture)); @@ -231,7 +231,7 @@ namespace MediaBrowser.Providers.TV } series.HomePageUrl = seriesInfo.homepage; - + series.RunTimeTicks = seriesInfo.episode_run_time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); if (string.Equals(seriesInfo.status, "Ended", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 91bb1cbe6..c02ad177f 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -384,11 +384,15 @@ namespace MediaBrowser.Server.Implementations.Channels { var val = width.Value; - return list + var res = list .OrderBy(i => i.Width.HasValue && i.Width.Value <= val) - .ThenBy(i => Math.Abs(i.Width ?? 0 - val)) + .ThenBy(i => Math.Abs((i.Width ?? 0) - val)) .ThenByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf); + .ThenBy(list.IndexOf) + .ToList(); + + + return res; } return list @@ -533,6 +537,11 @@ namespace MediaBrowser.Server.Implementations.Channels ? null : _userManager.GetUserById(new Guid(query.UserId)); + if (!string.IsNullOrWhiteSpace(query.UserId) && user == null) + { + throw new ArgumentException("User not found."); + } + var channels = _channels; if (query.ChannelIds.Length > 0) diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 59267e856..404c333c2 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,8 +1,13 @@ -using MediaBrowser.Common.Configuration; +using System.Linq; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; @@ -205,5 +210,55 @@ namespace MediaBrowser.Server.Implementations.Configuration } } } + + public void SetPreferredMetadataService(string service) + { + DisableMetadataService(typeof(Movie), Configuration, service); + DisableMetadataService(typeof(MusicAlbum), Configuration, service); + DisableMetadataService(typeof(MusicArtist), Configuration, service); + DisableMetadataService(typeof(Episode), Configuration, service); + DisableMetadataService(typeof(Season), Configuration, service); + DisableMetadataService(typeof(Series), Configuration, service); + DisableMetadataService(typeof(MusicVideo), Configuration, service); + DisableMetadataService(typeof(Trailer), Configuration, service); + DisableMetadataService(typeof(AdultVideo), Configuration, service); + DisableMetadataService(typeof(Video), Configuration, service); + } + + private void DisableMetadataService(Type type, ServerConfiguration config, string service) + { + var options = GetMetadataOptions(type, config); + + if (!options.DisabledMetadataSavers.Contains(service, StringComparer.OrdinalIgnoreCase)) + { + var list = options.DisabledMetadataSavers.ToList(); + + list.Add(service); + + options.DisabledMetadataSavers = list.ToArray(); + } + } + + private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config) + { + var options = config.MetadataOptions + .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase)); + + if (options == null) + { + var list = config.MetadataOptions.ToList(); + + options = new MetadataOptions + { + ItemType = type.Name + }; + + list.Add(options); + + config.MetadataOptions = list.ToArray(); + } + + return options; + } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 74ec325c6..2d7c798ad 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security //ExecuteBasic(req, res, requestDto); //first check if session is authenticated //if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) - //ValidateUser(req); + ValidateUser(req); } private void ValidateUser(IRequest req) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index 7e35d5d74..294e293b7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 5d06ba47e..740d1f6e7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 5fd59ee47..b9b3df10f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -215,5 +215,20 @@ "HeaderName": "N\u00e1zev", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kan\u00e1ly", + "HeaderMediaFolders": "Slo\u017eky m\u00e9di\u00ed", + "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" } \ 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 1d3f19568..21ceabda5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanaler", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 5b31baa33..546c8c1c4 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unbekannte Sprache", "ButtonMute": "Stumm", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "N\u00e4chster Track", "ButtonPause": "Pause", "ButtonPlay": "Abspielen", "ButtonEdit": "Bearbeiten", "ButtonQueue": "Warteschlange", "ButtonPlayTrailer": "Spiele Trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Vorheriger Track", "LabelEnabled": "Aktivieren", "LabelDisabled": "Deaktivieren", "ButtonMoreInformation": "mehr Informationen", @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kan\u00e4le", + "HeaderMediaFolders": "Medien Ordner", + "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" } \ 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 5305e1c88..4c441571d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 8f1c2cc5a..a3f0038c7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 ba55ada75..9ae851d95 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json index 1073d16cf..55fbf236d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma desconocido", "ButtonMute": "Silencio", "ButtonUnmute": "Activar audio", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pista siguiente", "ButtonPause": "Pausa", "ButtonPlay": "Reproducir", "ButtonEdit": "Editar", "ButtonQueue": "En cola", "ButtonPlayTrailer": "Reproducir trailer", "ButtonPlaylist": "Lista de reproducci\u00f3n", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Pista anterior", "LabelEnabled": "Activado", "LabelDisabled": "Desactivado", "ButtonMoreInformation": "M\u00e1s informaci\u00f3n", @@ -215,5 +215,20 @@ "HeaderName": "Nombre", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Artista del album", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "A\u00f1adido {0}", + "ButtonStart": "Inicio", + "HeaderChannels": "Canales", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin informaci\u00f3n de clasificaci\u00f3n:", + "OptionBlockOthers": "Otros", + "OptionBlockTvShows": "Tv Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "M\u00fasica", + "OptionBlockMovies": "Pel\u00edculas", + "OptionBlockBooks": "Libros", + "OptionBlockGames": "Juegos", + "OptionBlockLiveTvPrograms": "Programas de TV en vivo", + "OptionBlockLiveTvChannels": "Canales de Tv en vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet" } \ 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 1dbe5993c..3f87eeaae 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma Desconocido", "ButtonMute": "Mudo", "ButtonUnmute": "Quitar mudo", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pista Siguiente", "ButtonPause": "Pausar", "ButtonPlay": "Reproducir", "ButtonEdit": "Editar", "ButtonQueue": "A cola", "ButtonPlayTrailer": "Reproducir Avance", "ButtonPlaylist": "Lista de Reprod.", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Pista Anterior", "LabelEnabled": "Habilitado", "LabelDisabled": "Deshabilitado", "ButtonMoreInformation": "Mas Informaci\u00f3n", @@ -143,7 +143,7 @@ "HeaderSelectChannelDownloadPath": "Selecciona una ruta para la descarga del canal", "HeaderSelectChannelDownloadPathHelp": "Navega o entra en la ruta usada para almacenar los archivos temporales del canal. La carpeta debe tener permisos de escritura.", "OptionNewCollection": "Nuevo...", - "ButtonAdd": "A\u00f1adir", + "ButtonAdd": "Agregar", "ButtonRemove": "Eliminar", "LabelChapterDownloaders": "Descargadores de Cap\u00edtulos:", "LabelChapterDownloadersHelp": "Habilite y califique sus descargadores de cap\u00edtulos preferidos en orden de prioridad. Los descargadores con menor prioridad s\u00f3lo seran utilizados para completar informaci\u00f3n faltante.", @@ -215,5 +215,20 @@ "HeaderName": "Nombre", "HeaderAlbum": "\u00c1lbum", "HeaderAlbumArtist": "Artista del \u00c1lbum", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Agregado {0}", + "ButtonStart": "Iniciar", + "HeaderChannels": "Canales", + "HeaderMediaFolders": "Carpetas de Medios", + "HeaderBlockItemsWithNoRating": "Bloquear \u00edtems sin informaci\u00f3n de clasificaci\u00f3n", + "OptionBlockOthers": "Otros", + "OptionBlockTvShows": "Programas de TV", + "OptionBlockTrailers": "Avances", + "OptionBlockMusic": "M\u00fasica", + "OptionBlockMovies": "Pel\u00edculas", + "OptionBlockBooks": "Libros", + "OptionBlockGames": "Juegos", + "OptionBlockLiveTvPrograms": "Programas de TV en Vivo", + "OptionBlockLiveTvChannels": "Canales de TV en Vivo", + "OptionBlockChannelContent": "Contenido de Canales de Internet" } \ 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 aaf35f77c..7a020ab30 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Langue inconnue", "ButtonMute": "Sourdine", "ButtonUnmute": "D\u00e9sactiver sourdine", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Piste suivante", "ButtonPause": "Pause", "ButtonPlay": "Lire", "ButtonEdit": "Modifier", "ButtonQueue": "En file d'attente", "ButtonPlayTrailer": "Lire bande-annonce", "ButtonPlaylist": "Liste de lecture", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Piste pr\u00e9c\u00e9dante", "LabelEnabled": "Activ\u00e9", "LabelDisabled": "D\u00e9sactiv\u00e9", "ButtonMoreInformation": "Plus d'information", @@ -215,5 +215,20 @@ "HeaderName": "Nom", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Cha\u00eenes", + "HeaderMediaFolders": "R\u00e9pertoires de m\u00e9dias", + "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" } \ 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 29e6dc70f..59f4d83e9 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -215,5 +215,20 @@ "HeaderName": "\u05e9\u05dd", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "\u05e2\u05e8\u05d5\u05e6\u05d9\u05dd", + "HeaderMediaFolders": "\u05e1\u05e4\u05e8\u05d9\u05d5\u05ea \u05de\u05d3\u05d9\u05d4", + "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" } \ 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 fcbe94376..8ef22a770 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "(linguaggio sconosciuto)", "ButtonMute": "Muto", "ButtonUnmute": "Togli muto", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Prossimo", "ButtonPause": "Pausa", "ButtonPlay": "Riproduci", "ButtonEdit": "Modifica", "ButtonQueue": "In coda", "ButtonPlayTrailer": "Visualizza trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Precedente", "LabelEnabled": "Abilitato", "LabelDisabled": "Disabilitato", "ButtonMoreInformation": "Maggiori informazioni", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Artista Album", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "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" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json index 36588f2ec..22d1d91a3 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "\u0411\u0435\u043b\u0433\u0456\u0441\u0456\u0437 \u0442\u0456\u043b", "ButtonMute": "\u0414\u044b\u0431\u044b\u0441\u0442\u044b \u04e9\u0448\u0456\u0440\u0443", "ButtonUnmute": "\u0414\u044b\u0431\u044b\u0441\u0442\u044b \u049b\u043e\u0441\u0443", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "\u041a\u0435\u043b\u0435\u0441\u0456 \u0436\u043e\u043b\u0448\u044b\u049b", "ButtonPause": "\u04ae\u0437\u0456\u043b\u0456\u0441", "ButtonPlay": "\u041e\u0439\u043d\u0430\u0442\u0443", "ButtonEdit": "\u04e8\u04a3\u0434\u0435\u0443", "ButtonQueue": "\u041a\u0435\u0437\u0435\u043a", "ButtonPlayTrailer": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u0434\u0456 \u043e\u0439\u043d\u0430\u0442\u0443", "ButtonPlaylist": "\u041e\u0439\u043d\u0430\u0442\u0443 \u0442\u0456\u0437\u0456\u043c\u0456", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "\u0410\u043b\u0434\u044b\u04a3\u0493\u044b \u0436\u043e\u043b\u0448\u044b\u049b", "LabelEnabled": "\u049a\u043e\u0441\u044b\u043b\u0493\u0430\u043d", "LabelDisabled": "\u0410\u0436\u044b\u0440\u0430\u0442\u044b\u043b\u0493\u0430\u043d", "ButtonMoreInformation": "\u041a\u04e9\u0431\u0456\u0440\u0435\u043a \u0430\u049b\u043f\u0430\u0440\u0430\u0442", @@ -215,5 +215,20 @@ "HeaderName": "\u0410\u0442\u044b", "HeaderAlbum": "\u0410\u043b\u044c\u0431\u043e\u043c", "HeaderAlbumArtist": "\u0410\u043b\u044c\u0431\u043e\u043c \u043e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b\u0441\u044b", - "HeaderArtist": "\u041e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b" + "HeaderArtist": "\u041e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b", + "LabelAddedOnDate": "\u04ae\u0441\u0442\u0435\u043b\u0433\u0435\u043d\u0456 {0}", + "ButtonStart": "\u0411\u0430\u0441\u0442\u0430\u0443", + "HeaderChannels": "\u0410\u0440\u043d\u0430\u043b\u0430\u0440", + "HeaderMediaFolders": "\u0422\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b", + "HeaderBlockItemsWithNoRating": "\u0416\u0430\u0441\u0442\u044b\u049b \u0441\u0430\u043d\u0430\u0442\u044b \u0436\u043e\u049b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440\u0434\u0456 \u049b\u04b1\u0440\u0441\u0430\u0443\u043b\u0430\u0443:", + "OptionBlockOthers": "\u0411\u0430\u0441\u049b\u0430\u043b\u0430\u0440", + "OptionBlockTvShows": "\u0421\u0435\u0440\u0438\u0430\u043b\u0434\u0430\u0440", + "OptionBlockTrailers": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440", + "OptionBlockMusic": "\u041c\u0443\u0437\u044b\u043a\u0430", + "OptionBlockMovies": "\u0424\u0438\u043b\u044c\u043c\u0434\u0435\u0440", + "OptionBlockBooks": "\u041a\u0456\u0442\u0430\u043f\u0442\u0430\u0440", + "OptionBlockGames": "\u041e\u0439\u044b\u043d\u0434\u0430\u0440", + "OptionBlockLiveTvPrograms": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414-\u0442\u0430\u0440\u0430\u0442\u044b\u043c\u0434\u0430\u0440", + "OptionBlockLiveTvChannels": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414-\u0430\u0440\u043d\u0430\u043b\u0430\u0440", + "OptionBlockChannelContent": "\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b" } \ 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 fa186607a..b863a6c6e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 7f9156ef5..acf1182f7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 5545b69d2..e11fd37be 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Onbekende taal", "ButtonMute": "Dempen", "ButtonUnmute": "Dempen opheffen", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Volgend nummer", "ButtonPause": "Pauze", "ButtonPlay": "Afspelen", "ButtonEdit": "Bewerken", "ButtonQueue": "Wachtrij", "ButtonPlayTrailer": "Trailer Afspelen", "ButtonPlaylist": "Afspeellijst", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Vorig nummer", "LabelEnabled": "Ingeschakeld", "LabelDisabled": "Uitgeschakeld", "ButtonMoreInformation": "Meer informatie", @@ -215,5 +215,20 @@ "HeaderName": "Naam", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artiest", - "HeaderArtist": "Artiest" + "HeaderArtist": "Artiest", + "LabelAddedOnDate": "Toegevoegd {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanalen", + "HeaderMediaFolders": "Media Mappen", + "HeaderBlockItemsWithNoRating": "Blokkeren van onderdelen zonder classificatiegegevens:", + "OptionBlockOthers": "Overigen", + "OptionBlockTvShows": "TV Series", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Muziek", + "OptionBlockMovies": "Films", + "OptionBlockBooks": "Boeken", + "OptionBlockGames": "Spellen", + "OptionBlockLiveTvPrograms": "Live TV Programma's", + "OptionBlockLiveTvChannels": "Live TV Kanalen", + "OptionBlockChannelContent": "Internet kanaal Inhoud" } \ 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 b29940c31..729e20b31 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 225172c81..16407aa48 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma desconhecido", "ButtonMute": "Mudo", "ButtonUnmute": "Remover Mudo", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pr\u00f3xima faixa", "ButtonPause": "Pausar", "ButtonPlay": "Reproduzir", "ButtonEdit": "Editar", "ButtonQueue": "Fila", "ButtonPlayTrailer": "Reproduzir trailer", "ButtonPlaylist": "Lista reprodu\u00e7\u00e3o", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Faixa anterior", "LabelEnabled": "Ativada", "LabelDisabled": "Desativada", "ButtonMoreInformation": "Mais informa\u00e7\u00f5es", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "\u00c1lbum", "HeaderAlbumArtist": "Artista do \u00c1lbum", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Canais", + "HeaderMediaFolders": "Pastas de M\u00eddia", + "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" } \ 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 6adf54fbd..9ca378a91 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pr\u00f3xima Faixa", "ButtonPause": "Pausar", "ButtonPlay": "Reproduzir", "ButtonEdit": "Editar", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Faixa Anterior", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Canais", + "HeaderMediaFolders": "Pastas Multim\u00e9dia", + "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" } \ 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 0d6bc9e83..20eede541 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -38,7 +38,7 @@ "LabelEpisode": "\u042d\u043f\u0438\u0437\u043e\u0434", "LabelSeries": "\u0421\u0435\u0440\u0438\u0430\u043b", "LabelStopping": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e", - "ButtonStop": "\u0421\u0442\u043e\u043f", + "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", "LabelCancelled": "(\u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e)", "LabelFailed": "(\u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e)", "LabelAbortedByServerShutdown": "(\u041f\u0440\u0435\u0440\u0432\u0430\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0430)", @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "\u041d\u0435\u043e\u043f\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u044f\u0437\u044b\u043a", "ButtonMute": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0432\u0443\u043a", "ButtonUnmute": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0432\u0443\u043a", - "ButtonNextTrack": "Next Track", + "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", "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", "ButtonPlaylist": "\u0421\u043f\u0438\u0441\u043e\u043a \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f", - "ButtonPreviousTrack": "Previous Track", + "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", @@ -215,5 +215,20 @@ "HeaderName": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "HeaderAlbum": "\u0410\u043b\u044c\u0431\u043e\u043c", "HeaderAlbumArtist": "\u0410\u043b\u044c\u0431\u043e\u043c\u043d\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c", - "HeaderArtist": "\u0418\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c" + "HeaderArtist": "\u0418\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c", + "LabelAddedOnDate": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e {0}", + "ButtonStart": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c", + "HeaderChannels": "\u041a\u0430\u043d\u0430\u043b\u044b", + "HeaderMediaFolders": "\u041c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0438", + "HeaderBlockItemsWithNoRating": "\u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0431\u0435\u0437 \u0432\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u043e\u0439 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438:", + "OptionBlockOthers": "\u0414\u0440\u0443\u0433\u0438\u0435", + "OptionBlockTvShows": "\u0421\u0435\u0440\u0438\u0430\u043b\u044b", + "OptionBlockTrailers": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u044b", + "OptionBlockMusic": "\u041c\u0443\u0437\u044b\u043a\u0430", + "OptionBlockMovies": "\u0424\u0438\u043b\u044c\u043c\u044b", + "OptionBlockBooks": "\u041a\u043d\u0438\u0433\u0438", + "OptionBlockGames": "\u0418\u0433\u0440\u044b", + "OptionBlockLiveTvPrograms": "\u042d\u0444\u0438\u0440\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438", + "OptionBlockLiveTvChannels": "\u042d\u0444\u0438\u0440\u043d\u044b\u0435 \u0422\u0412-\u043a\u0430\u043d\u0430\u043b\u044b", + "OptionBlockChannelContent": "\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u043a\u0430\u043d\u0430\u043b\u043e\u0432" } \ 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 845621b10..153160c4a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Ok\u00e4nt spr\u00e5k", "ButtonMute": "Tyst", "ButtonUnmute": "Muting av", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "N\u00e4sta sp\u00e5r", "ButtonPause": "Paus", "ButtonPlay": "Spela upp", "ButtonEdit": "\u00c4ndra", "ButtonQueue": "K\u00f6", "ButtonPlayTrailer": "Spela upp trailer", "ButtonPlaylist": "Spellista", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "F\u00f6reg\u00e5ende sp\u00e5r", "LabelEnabled": "Aktiverad", "LabelDisabled": "Avaktiverad", "ButtonMoreInformation": "Mer information", @@ -215,5 +215,20 @@ "HeaderName": "Namn", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Albumartist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanaler", + "HeaderMediaFolders": "Mediamappar", + "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" } \ 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 8a10fdc55..7405894d4 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -215,5 +215,20 @@ "HeaderName": "T\u00ean", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "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" } \ 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 abf7a395a..1da78b98d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "\u983b\u5ea6", + "HeaderMediaFolders": "\u5a92\u9ad4\u6587\u4ef6\u593e", + "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" } \ 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 53c3edfb1..ecb6abd64 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 65c50df85..35f607be1 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 1f1bea554..5d25ef870 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -479,7 +479,7 @@ "HeaderProgram": "Program", "HeaderClients": "Klienti", "LabelCompleted": "Hotovo", - "LabelFailed": "Chyba", + "LabelFailed": "Failed", "LabelSkipped": "P\u0159esko\u010deno", "HeaderEpisodeOrganization": "Episode Organization", "LabelSeries": "Series:", @@ -490,7 +490,7 @@ "HeaderSupportTheTeam": "Podpo\u0159te t\u00edm Media Browser", "LabelSupportAmount": "Suma (USD)", "HeaderSupportTheTeamHelp": "Pomozte zajistit pokra\u010dov\u00e1n\u00ed v\u00fdvoje tohoto projektu t\u00edm, \u017ee daruje. \u010c\u00e1st v\u0161ech dar\u016f bude pou\u017eita na dal\u0161\u00ed bezplatn\u00e9 n\u00e1stroje na kter\u00fdch jsme z\u00e1visl\u00ed.", - "ButtonEnterSupporterKey": "Enter supporter key", + "ButtonEnterSupporterKey": "Vlo\u017ete kl\u00ed\u010d sponzora", "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.", @@ -529,7 +529,7 @@ "ButtonShutdown": "Vypnout", "ButtonUpdateNow": "Update Now", "PleaseUpdateManually": "Please shutdown the server and update manually.", - "NewServerVersionAvailable": "A new version of Media Browser Server is available!", + "NewServerVersionAvailable": "Je dostupn\u00e1 nov\u00e1 verze programu Media Browser!", "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:", @@ -537,14 +537,14 @@ "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", + "LabelOldSupporterKey": "Star\u00fd kl\u00ed\u010d sponzora", + "LabelNewSupporterKey": "Nov\u00fd kl\u00ed\u010d sponzora", "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", + "LabelCurrentEmailAddress": "Aktu\u00e1ln\u00ed e-mailov\u00e1 adresa", "LabelCurrentEmailAddressHelp": "The current email address to which your new key was sent.", "HeaderForgotKey": "Forgot Key", - "LabelEmailAddress": "Email address", + "LabelEmailAddress": "E-mailov\u00e1 adresa", "LabelSupporterEmailAddress": "The email address that was used to purchase the key.", "ButtonRetrieveKey": "Retrieve Key", "LabelSupporterKey": "Supporter Key (paste from email)", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 c5bf75c0f..8f7bca786 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 151156fbf..033829986 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -479,10 +479,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", @@ -630,8 +630,8 @@ "ButtonScenes": "Szenen", "ButtonSubtitles": "Untertitel", "ButtonAudioTracks": "Audiospuren", - "ButtonPreviousTrack": "Vorheriger Track", - "ButtonNextTrack": "N\u00e4chster Track", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 f590a2f64..fb43d1877 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 051387ea9..202a37682 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 60557cdca..764685deb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json index fbd39b960..5a631a8d6 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json @@ -479,10 +479,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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login renuncia:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", - "LabelAutomaticallyDonateHelp": "Usted puede cancelar en cualquier momento desde su cuenta de PayPal." + "LabelAutomaticallyDonateHelp": "Usted puede cancelar en cualquier momento desde su cuenta de PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 c8f3bcb05..e5390cef5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -132,7 +132,7 @@ "OptionReleaseDate": "Fecha de Estreno", "OptionPlayCount": "N\u00famero de Reproducc.", "OptionDatePlayed": "Fecha de Reproducci\u00f3n", - "OptionDateAdded": "A\u00f1adido el", + "OptionDateAdded": "Agregardo el", "OptionAlbumArtist": "Artista del \u00c1lbum", "OptionArtist": "Artista", "OptionAlbum": "\u00c1lbum", @@ -234,7 +234,7 @@ "ButtonSelect": "Seleccionar", "ButtonSearch": "B\u00fasqueda", "ButtonGroupVersions": "Agrupar Versiones", - "ButtonAddToCollection": "Agregar a Colecci\u00f3n.", + "ButtonAddToCollection": "Agregar a Colecci\u00f3n", "PismoMessage": "Utilizando Pismo File Mount a trav\u00e9s de una licencia donada.", "TangibleSoftwareMessage": "Utilizando convertidores Java\/C# de Tangible Solutions por medio de una licencia donada.", "HeaderCredits": "Cr\u00e9ditos", @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a fanart.tv. Las Im\u00e1genes existentes no ser\u00e1n reemplazadas.", "LabelAutomaticUpdatesTmdbHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a TheMovieDB.org. Las im\u00e1genes existentes no ser\u00e1n reemplazadas.", "LabelAutomaticUpdatesTvdbHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a TheTVDB.com. Las im\u00e1genes existentes no ser\u00e1n reemplazadas.", - "ExtractChapterImagesHelp": "Extraer im\u00e1genes de cap\u00edtulos permite a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, intensivo en utilizaci\u00f3n del CPU y puede requerir varios gigabytes de espacio. Se ejecuta como una tarea nocturna, programada a las 4 de la ma\u00f1ana, aunque esto se puede configurar en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante las horas pico de uso.", + "ExtractChapterImagesHelp": "Extraer im\u00e1genes de cap\u00edtulos permite a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, intensivo en utilizaci\u00f3n del CPU y puede requerir varios gigabytes de espacio. Se ejecuta cuando se detectan nuevos videos, tambi\u00e9n como una tarea nocturna, programada a las 4:00 am. La programaci\u00f3n puede configurarse en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante horas pico de uso.", "LabelMetadataDownloadLanguage": "Lenguaje preferido para descargas:", "ButtonAutoScroll": "Auto-desplazamiento", "LabelImageSavingConvention": "Convenci\u00f3n de almacenamiento de im\u00e1genes:", @@ -353,9 +353,9 @@ "LabelMaxScreenshotsPerItem": "N\u00famero m\u00e1ximo de capturas de pantalla por \u00edtem:", "LabelMinBackdropDownloadWidth": "Anchura m\u00ednima de descarga de im\u00e1genes de fondo:", "LabelMinScreenshotDownloadWidth": "Anchura m\u00ednima de descarga de capturas de pantalla:", - "ButtonAddScheduledTaskTrigger": "A\u00f1adir Evento", - "HeaderAddScheduledTaskTrigger": "A\u00f1adir Evento", - "ButtonAdd": "A\u00f1adir", + "ButtonAddScheduledTaskTrigger": "Agregar Disparador de Tarea", + "HeaderAddScheduledTaskTrigger": "Agregar Disparador de Tarea", + "ButtonAdd": "Agregar", "LabelTriggerType": "Tipo de Evento:", "OptionDaily": "Diario", "OptionWeekly": "Semanal", @@ -406,7 +406,7 @@ "LabelFromHelp": "Ejemplo: D:\\Pel\u00edculas (en el servidor)", "LabelTo": "Hasta:", "LabelToHelp": "Ejemplo: \\\\MiServidor\\Pel\u00edculas (una ruta a la que los clientes pueden acceder)", - "ButtonAddPathSubstitution": "A\u00f1adir Ruta Alternativa", + "ButtonAddPathSubstitution": "Agregar Ruta Alternativa", "OptionSpecialEpisode": "Especiales", "OptionMissingEpisode": "Episodios Faltantes", "OptionUnairedEpisode": "Episodios no Emitidos", @@ -425,7 +425,7 @@ "OptionEnableDebugTranscodingLoggingHelp": "Esto crear\u00e1 archivos de bit\u00e1cora muy grandes y solo se recomienda cuando se requiera solucionar problemas.", "OptionUpscaling": "Permitir que los clientes solicitar v\u00eddeo de escala aumentada", "OptionUpscalingHelp": "En algunos casos esto resultar\u00e1 en una mejora de la calidad del video pero incrementar\u00e1 el uso del CPU.", - "EditCollectionItemsHelp": "A\u00f1adir o quitar pel\u00edculas, series, discos, libros o juegos que usted desee agrupar dentro de esta colecci\u00f3n.", + "EditCollectionItemsHelp": "Agregar o quitar pel\u00edculas, series, discos, libros o juegos que usted desee agrupar dentro de esta colecci\u00f3n.", "HeaderAddTitles": "Agregar T\u00edtulos", "LabelEnableDlnaPlayTo": "Habilitar \"Reproducir en\" por DLNA", "LabelEnableDlnaPlayToHelp": "Media Browser puede detectar dispositivos en su red y ofrecer la posibilidad de controlarlos remotamente.", @@ -587,8 +587,8 @@ "NotificationOptionGamePlaybackStopped": "Ejecuci\u00f3n de juego detenida", "NotificationOptionTaskFailed": "Falla de tarea programada", "NotificationOptionInstallationFailed": "Falla de instalaci\u00f3n", - "NotificationOptionNewLibraryContent": "Adici\u00f3n de nuevos contenidos", - "NotificationOptionNewLibraryContentMultiple": "Nuevo contenido agregado (varios).", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionNewLibraryContentMultiple": "Nuevo contenido agregado (varios)", "SendNotificationHelp": "Por defecto, las notificaciones son enviadas a la bandeja de entrada del panel de control. Navegue el cat\u00e1logo de complementos para instalar opciones de notificaci\u00f3n adicionales.", "NotificationOptionServerRestartRequired": "Reinicio del servidor requerido", "LabelNotificationEnabled": "Habilitar esta notificaci\u00f3n", @@ -630,8 +630,8 @@ "ButtonScenes": "Escenas", "ButtonSubtitles": "Subt\u00edtulos", "ButtonAudioTracks": "Pistas de audio", - "ButtonPreviousTrack": "Pista Anterior", - "ButtonNextTrack": "Pista Siguiente", + "ButtonPreviousTrack": "Pista anterior", + "ButtonNextTrack": "Pista siguiente", "ButtonStop": "Detener", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Agrupar pel\u00edculas en colecciones", @@ -702,8 +702,8 @@ "LabelSerialNumber": "N\u00famero de serie:", "LabelDeviceDescription": "Descripci\u00f3n del dispositivo", "HeaderIdentificationCriteriaHelp": "Capture, al menos, un criterio de identificaci\u00f3n.", - "HeaderDirectPlayProfileHelp": "A\u00f1ada perfiles de reproducci\u00f3n directa para indicar que formatos puede manejar el dispositivo de manera nativa.", - "HeaderTranscodingProfileHelp": "A\u00f1ada perfiles de transcodificaci\u00f3n para indicar que formatos deben ser usados cuando se requiera transcodificar.", + "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducci\u00f3n directa para indicar que formatos puede manejar el dispositivo de manera nativa.", + "HeaderTranscodingProfileHelp": "Agruegue perfiles de transcodificaci\u00f3n para indicar que formatos deben ser usados cuando se requiera transcodificar.", "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la informaci\u00f3n enviada a un dispositivo cuando se reproducen ciertos tipos de medios.", "LabelXDlnaCap": "X-DLNA cap:", "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", @@ -824,7 +824,7 @@ "LabelProtocolInfoHelp": "El valor que ser\u00e1 utilizado cuando se responde a solicitudes GetProtocolInfo desde el dispositivo.", "TabXbmcMetadata": "Xbmc", "HeaderXbmcMetadataHelp": "Media Browser incluye soporte nativo para metadatos Nfo e im\u00e1genes de Xbmc. Para habilitar o deshabilitar metadatos de Xbmc, utilice la pesta\u00f1a Avanzado para configurar opciones para sus tipos de medios.", - "LabelXbmcMetadataUser": "A\u00f1adir usuario de monitoreo de datos a los nfo\u00b4s para:", + "LabelXbmcMetadataUser": "Agregar usuario de monitoreo de datos a los nfo\u00b4s para:", "LabelXbmcMetadataUserHelp": "Habilitar esto para mantener el monitoreo de datos en sincron\u00eda entre Media Browser y Xbmc.", "LabelXbmcMetadataDateFormat": "Formato de fecha de esteno:", "LabelXbmcMetadataDateFormatHelp": "Todas las fechas en los archivos nfo's ser\u00e1n le\u00eddas y escritas empleando este formato.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Aviso de Inicio de Sesi\u00f3n:", "LabelLoginDisclaimerHelp": "Esto se mostrara al final de la pagina de inicio de sesi\u00f3n.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", - "LabelAutomaticallyDonateHelp": "Puedes cancelarlo en cualquier momento por medio de tu cuenta PayPal." + "LabelAutomaticallyDonateHelp": "Puedes cancelarlo en cualquier momento por medio de tu cuenta PayPal.", + "OptionList": "Lista", + "TabDashboard": "Panel de Control", + "TitleServer": "Servidor", + "LabelCache": "Cach\u00e9:", + "LabelLogs": "Bit\u00e1coras:", + "LabelMetadata": "Metadatos:", + "LabelImagesByName": "Im\u00e1genes por nombre:", + "LabelTranscodingTemporaryFiles": "Archivos temporales de transcodificaci\u00f3n:" } \ 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 05b8f568d..90a412bfc 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -396,7 +396,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 +479,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", @@ -630,8 +630,8 @@ "ButtonScenes": "Sc\u00e8nes", "ButtonSubtitles": "Sous-titres", "ButtonAudioTracks": "Piste audio", - "ButtonPreviousTrack": "Piste pr\u00e9c\u00e9dante", - "ButtonNextTrack": "Piste suivante", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Arr\u00eat", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Grouper les films en collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 55ed199e1..7ab9173ae 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -396,7 +396,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 +479,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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 591fe98b9..026772da5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -479,10 +479,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:", @@ -630,8 +630,8 @@ "ButtonScenes": "Scene", "ButtonSubtitles": "Sottotitoli", "ButtonAudioTracks": "Traccia Audio", - "ButtonPreviousTrack": "Precedente", - "ButtonNextTrack": "Prossimo", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pausa", "LabelGroupMoviesIntoCollections": "Raggruppa i film nelle collection", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "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." + "LabelAutomaticallyDonateHelp": "\u00c8 possibile annullare in qualsiasi momento tramite il vostro conto PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 f7e73eb8f..8ec25aea4 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -795,7 +795,7 @@ "MessageLearnHowToCustomize": "\u041e\u0441\u044b \u0431\u0435\u0442\u0442\u0456 \u0436\u0435\u043a\u0435 \u043a\u04e9\u04a3\u0456\u043b\u0433\u0435 \u04b1\u043d\u0430\u0442\u0443 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u049b\u0430\u043b\u0430\u0439 \u0442\u0435\u04a3\u0448\u0435\u0443\u0456\u043d \u04af\u0439\u0440\u0435\u043d\u0456\u04a3\u0456\u0437. \u0422\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440\u0456\u04a3\u0456\u0437\u0434\u0456 \u049b\u0430\u0440\u0430\u0443 \u0436\u04d9\u043d\u0435 \u0436\u0430\u04a3\u0430\u0440\u0442\u0443 \u04af\u0448\u0456\u043d \u044d\u043a\u0440\u0430\u043d\u0434\u0430\u0493\u044b \u0436\u043e\u0493\u0430\u0440\u0493\u044b \u043e\u04a3 \u0431\u04b1\u0440\u044b\u0448\u044b\u043d\u0434\u0430\u0493\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u044b\u049b \u0431\u0435\u043b\u0433\u0456\u0448\u0435\u04a3\u0456\u0437\u0434\u0456 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437.", "ButtonEditOtherUserPreferences": "\u041e\u0441\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440\u0456\u043d \u04e9\u04a3\u0434\u0435\u0443.", "LabelChannelStreamQuality": "\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0430\u0440\u049b\u044b\u043b\u044b \u0430\u0493\u044b\u043d\u043c\u0435\u043d \u0442\u0430\u0441\u044b\u043c\u0430\u043b\u0434\u0430\u0443 \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0456:", - "LabelChannelStreamQualityHelp": "\u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u043b\u0430\u0440\u0434\u0430, \u0441\u0430\u043f\u0430\u043d\u044b \u0448\u0435\u043a\u0442\u0435\u0443 \u0436\u0430\u0442\u044b\u049b \u0430\u0493\u044b\u043d\u043c\u0435\u043d \u0442\u0430\u0441\u044b\u043c\u0430\u043b\u0434\u0430\u0443 \u04af\u0448\u0456\u043d \u049b\u0430\u043c\u0442\u0430\u043c\u0430\u0441\u044b\u0437 \u0435\u0442\u0433\u0435 \u043a\u04e9\u043c\u0435\u043a\u0442\u0435\u0441\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d.", + "LabelChannelStreamQualityHelp": "\u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u0434\u0430, \u0441\u0430\u043f\u0430\u0441\u044b\u043d \u0448\u0435\u043a\u0442\u0435\u0443\u0456 \u0436\u0430\u0442\u044b\u049b\u0442\u0430\u0443 \u0430\u0493\u044b\u043d\u0434\u044b\u049b \u043e\u0439\u043d\u0430\u0442\u0443 \u04d9\u0441\u0435\u0440\u0456\u043d \u049b\u0430\u043c\u0442\u0430\u043c\u0430\u0441\u044b\u0437 \u0435\u0442\u0443\u0456\u043d\u0435 \u043a\u04e9\u043c\u0435\u043a\u0442\u0435\u0441\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d.", "OptionBestAvailableStreamQuality": "\u049a\u043e\u043b \u0436\u0435\u0442\u0456\u043c\u0434\u0456 \u0435\u04a3 \u0436\u0430\u049b\u0441\u044b", "LabelEnableChannelContentDownloadingFor": "\u0411\u04b1\u043b \u04af\u0448\u0456\u043d \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u0441\u0443:", "LabelEnableChannelContentDownloadingForHelp": "\u041a\u0435\u0439\u0431\u0456\u0440 \u0430\u0440\u043d\u0430\u043b\u0430\u0440 \u049b\u0430\u0440\u0430\u0443\u0434\u044b\u04a3 \u0430\u043b\u0434\u044b\u043d\u0434\u0430 \u043c\u0430\u0437\u043c\u04af\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u043b\u0434\u0430\u0439\u0434\u044b. \u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u043b\u0430\u0440\u0434\u0430 \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04b1\u043c\u044b\u0441\u0442\u0430\u043d \u0431\u043e\u0441 \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043d\u044b \u049b\u043e\u0441\u044b\u04a3\u044b\u0437. \u041c\u0430\u0437\u043c\u04af\u043d \u0430\u0440\u043d\u0430 \u0436\u04af\u043a\u0442\u0435\u0443 \u0436\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d \u0442\u0430\u043f\u0441\u044b\u0440\u043c\u0430\u0441\u044b \u0431\u04e9\u043b\u0456\u0433\u0456 \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0436\u04af\u043a\u0442\u0435\u043b\u0435\u0434\u0456.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "\u041a\u0456\u0440\u0433\u0435\u043d\u0434\u0435\u0433\u0456 \u0435\u0441\u043a\u0435\u0440\u0442\u0443:", "LabelLoginDisclaimerHelp": "\u0411\u04b1\u043b \u043a\u0456\u0440\u0443 \u0431\u0435\u0442\u0456\u043d\u0456\u04a3 \u0442\u04e9\u043c\u0435\u043d\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0434\u0456.", "LabelAutomaticallyDonate": "\u041e\u0441\u044b \u0441\u043e\u043c\u0430\u043d\u044b \u0430\u0439 \u0441\u0430\u0439\u044b\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u0441\u044b\u0439\u043b\u0430\u0443", - "LabelAutomaticallyDonateHelp": "PayPal \u0435\u0441\u0435\u043f \u0448\u043e\u0442\u044b\u04a3\u044b\u0437 \u0430\u0440\u049b\u044b\u043b\u044b \u043a\u0435\u0437 \u043a\u0435\u043b\u0433\u0435\u043d \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0434\u043e\u0493\u0430\u0440\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456\u04a3\u0456\u0437 \u0431\u0430\u0440." + "LabelAutomaticallyDonateHelp": "PayPal \u0435\u0441\u0435\u043f \u0448\u043e\u0442\u044b\u04a3\u044b\u0437 \u0430\u0440\u049b\u044b\u043b\u044b \u043a\u0435\u0437 \u043a\u0435\u043b\u0433\u0435\u043d \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0434\u043e\u0493\u0430\u0440\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456\u04a3\u0456\u0437 \u0431\u0430\u0440.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 b790d5660..cbde8c27c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 394769b2a..1bf329cd2 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 6c5842658..e7094e288 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -630,8 +630,8 @@ "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", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Dit wordt onderaan de login pagina weergegeven.", "LabelAutomaticallyDonate": "Doneer dit bedrag automatisch per maand", - "LabelAutomaticallyDonateHelp": "U kunt dit via uw PayPal account op elk moment annuleren." + "LabelAutomaticallyDonateHelp": "U kunt dit via uw PayPal account op elk moment annuleren.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 b4d49e5b3..aad8274bf 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 96c3429c9..ea1ed0400 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -846,5 +846,13 @@ "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 m\u00eas", - "LabelAutomaticallyDonateHelp": "Voc\u00ea pode cancelar a qualquer hora atrav\u00e9s de sua conta do PayPal." + "LabelAutomaticallyDonateHelp": "Voc\u00ea pode cancelar a qualquer hora atrav\u00e9s de sua conta do PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 87c24c117..dfc818e7d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -479,10 +479,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", @@ -630,8 +630,8 @@ "ButtonScenes": "Cenas", "ButtonSubtitles": "Legendas", "ButtonAudioTracks": "Faixas de \u00e1udio", - "ButtonPreviousTrack": "Faixa Anterior", - "ButtonNextTrack": "Pr\u00f3xima Faixa", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Parar", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Group movies into collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 c1150bc2b..eb58b98bc 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -479,7 +479,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:", @@ -632,7 +632,7 @@ "ButtonAudioTracks": "\u0410\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0438", "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": "\u0421\u0442\u043e\u043f", + "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", "ButtonPause": "\u041f\u0430\u0443\u0437\u0430", "LabelGroupMoviesIntoCollections": "\u0413\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u043c\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439", "LabelGroupMoviesIntoCollectionsHelp": "\u041f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0444\u0438\u043b\u044c\u043c\u043e\u0432, \u0444\u0438\u043b\u044c\u043c\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u043a\u0430\u043a \u0435\u0434\u0438\u043d\u044b\u0439 \u0441\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442.", @@ -795,7 +795,7 @@ "MessageLearnHowToCustomize": "\u0423\u0437\u043d\u0430\u0439\u0442\u0435, \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c\u0443 \u0432\u043a\u0443\u0441\u0443. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u0430\u0432\u0430\u0442\u0430\u0440\u0443 \u0432 \u043f\u0440\u0430\u0432\u043e\u043c \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0433\u043b\u0443 \u044d\u043a\u0440\u0430\u043d\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", "ButtonEditOtherUserPreferences": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c \u043b\u0438\u0447\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "LabelChannelStreamQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442:", - "LabelChannelStreamQualityHelp": "\u0412 \u0441\u0440\u0435\u0434\u0435 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043b\u0430\u0432\u043d\u0443\u044e \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438.", + "LabelChannelStreamQualityHelp": "\u0412 \u0441\u0440\u0435\u0434\u0435 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.", "OptionBestAvailableStreamQuality": "\u041d\u0430\u0438\u043b\u0443\u0447\u0448\u0435\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435", "LabelEnableChannelContentDownloadingFor": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430 \u0434\u043b\u044f:", "LabelEnableChannelContentDownloadingForHelp": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435. \u0412\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043f\u0440\u0438 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0432\u0440\u0435\u043c\u044f. \u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u043d\u0430\u043b\u043e\u0432.", @@ -846,5 +846,13 @@ "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\u0439 \u043c\u0435\u0441\u044f\u0446", - "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." + "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": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 e7e168562..bc350bb38 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -861,5 +861,14 @@ "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", - "OptionList": "List" + "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" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index c2e49294f..810629422 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -479,10 +479,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:", @@ -630,8 +630,8 @@ "ButtonScenes": "Scener", "ButtonSubtitles": "Undertexter", "ButtonAudioTracks": "Ljudsp\u00e5r", - "ButtonPreviousTrack": "F\u00f6reg\u00e5ende sp\u00e5r", - "ButtonNextTrack": "N\u00e4sta sp\u00e5r", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stopp", "ButtonPause": "Paus", "LabelGroupMoviesIntoCollections": "Gruppera filmer i samlingsboxar", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Ansvarsbegr\u00e4nsning vid inloggning:", "LabelLoginDisclaimerHelp": "Detta visas l\u00e4ngst ned p\u00e5 inloggningssidan.", "LabelAutomaticallyDonate": "Donera automatiskt detta belopp varje m\u00e5nad", - "LabelAutomaticallyDonateHelp": "Du kan avbryta n\u00e4r som helst via ditt PayPal-konto." + "LabelAutomaticallyDonateHelp": "Du kan avbryta n\u00e4r som helst via ditt PayPal-konto.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ 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 269d7a8bc..0da205c7b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -279,7 +279,7 @@ "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 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.", + "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:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ 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 2868ad3d9..78c7689c3 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -396,7 +396,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.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "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:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index c3d24c0de..338bf8959 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1210,9 +1210,16 @@ namespace MediaBrowser.Server.Implementations.Session /// Task{SessionInfo}. /// Invalid user or password entered. /// - public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) + public async Task AuthenticateNewSession(string username, + string password, + string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint) { - var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = (IsLocalhost(remoteEndPoint) && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || + await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { @@ -1337,7 +1344,7 @@ namespace MediaBrowser.Server.Implementations.Session remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); } - public bool IsLocal(string remoteEndpoint) + public bool IsInLocalNetwork(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 688c07639..96e38b2d4 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -17,9 +17,6 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -45,7 +42,6 @@ using MediaBrowser.LocalMetadata.Providers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Subtitles; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; @@ -343,58 +339,13 @@ namespace MediaBrowser.ServerApplication ? "Xbmc Nfo" : "Media Browser Xml"; - DisableMetadataService(typeof(Movie), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicAlbum), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicArtist), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Episode), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Season), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Series), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicVideo), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Trailer), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(AdultVideo), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Video), ServerConfigurationManager.Configuration, service); + ServerConfigurationManager.SetPreferredMetadataService(service); } ServerConfigurationManager.Configuration.DefaultMetadataSettingsApplied = true; ServerConfigurationManager.SaveConfiguration(); } - private void DisableMetadataService(Type type, ServerConfiguration config, string service) - { - var options = GetMetadataOptions(type, config); - - if (!options.DisabledMetadataSavers.Contains(service, StringComparer.OrdinalIgnoreCase)) - { - var list = options.DisabledMetadataSavers.ToList(); - - list.Add(service); - - options.DisabledMetadataSavers = list.ToArray(); - } - } - - private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config) - { - var options = config.MetadataOptions - .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase)); - - if (options == null) - { - var list = config.MetadataOptions.ToList(); - - options = new MetadataOptions - { - ItemType = type.Name - }; - - list.Add(options); - - config.MetadataOptions = list.ToArray(); - } - - return options; - } - private void DeleteDeprecatedModules() { try diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 36adae71c..186cf9fe3 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -521,7 +521,6 @@ namespace MediaBrowser.WebDashboard.Api "mediacontroller.js", "chromecast.js", "backdrops.js", - "branding.js", "mediaplayer.js", "mediaplayer-video.js", @@ -544,7 +543,6 @@ namespace MediaBrowser.WebDashboard.Api "channelitems.js", "channelsettings.js", "dashboardgeneral.js", - "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", "dlnaprofile.js", @@ -627,6 +625,7 @@ namespace MediaBrowser.WebDashboard.Api "scheduledtaskpage.js", "scheduledtaskspage.js", "search.js", + "serversecurity.js", "songs.js", "supporterkeypage.js", "supporterpage.js", @@ -645,7 +644,6 @@ namespace MediaBrowser.WebDashboard.Api "userprofilespage.js", "userparentalcontrol.js", "wizardfinishpage.js", - "wizardimagesettings.js", "wizardservice.js", "wizardstartpage.js", "wizardsettings.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 2906d1c34..e08da4e00 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -98,9 +98,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -302,9 +299,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -620,9 +614,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -641,9 +632,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -794,6 +782,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -803,9 +794,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -815,6 +803,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1442,9 +1433,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest -- cgit v1.2.3 From 98012480f70cdfa918bbea1ae975d6f7cb8c7f67 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 14 Jul 2014 21:25:58 -0400 Subject: fix ipad login issue --- MediaBrowser.Api/BaseApiService.cs | 12 +++++++++++- MediaBrowser.Api/UserService.cs | 17 +++++++++++++++++ MediaBrowser.Controller/Entities/Folder.cs | 4 +++- MediaBrowser.Controller/Entities/Studio.cs | 9 ++++++++- .../HttpServer/Security/AuthService.cs | 6 ++++++ 5 files changed, 45 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 09eb1ea41..727ee6fbc 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Api return ResultFactory.GetStaticFileResult(Request, path); } - private readonly char[] _dashReplaceChars = new[] { '?', '/' }; + private readonly char[] _dashReplaceChars = { '?', '/' }; private const char SlugChar = '-'; protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) @@ -168,6 +168,11 @@ namespace MediaBrowser.Api { var user = userManager.GetUserById(userId.Value); + if (user == null) + { + throw new ArgumentException("User not found"); + } + return folder.GetRecursiveChildren(user); } @@ -177,6 +182,11 @@ namespace MediaBrowser.Api { var user = userManager.GetUserById(userId.Value); + if (user == null) + { + throw new ArgumentException("User not found"); + } + return userManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user); } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index df4bc06ba..b7a43c237 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -357,6 +357,23 @@ namespace MediaBrowser.Api { var auth = AuthorizationContext.GetAuthorizationInfo(Request); + if (string.IsNullOrWhiteSpace(auth.Client)) + { + auth.Client = "Unknown app"; + } + if (string.IsNullOrWhiteSpace(auth.Device)) + { + auth.Device = "Unknown device"; + } + if (string.IsNullOrWhiteSpace(auth.Version)) + { + auth.Version = "Unknown version"; + } + if (string.IsNullOrWhiteSpace(auth.DeviceId)) + { + 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; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index b886cef19..12afe26b6 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -21,12 +21,13 @@ namespace MediaBrowser.Controller.Entities /// /// Class Folder /// - public class Folder : BaseItem, IHasThemeMedia + public class Folder : BaseItem, IHasThemeMedia, IHasTags { public static IUserManager UserManager { get; set; } public List ThemeSongIds { get; set; } public List ThemeVideoIds { get; set; } + public List Tags { get; set; } public Folder() { @@ -34,6 +35,7 @@ namespace MediaBrowser.Controller.Entities ThemeSongIds = new List(); ThemeVideoIds = new List(); + Tags = new List(); } /// diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 8271a3df2..0d934ad0a 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -7,8 +7,15 @@ namespace MediaBrowser.Controller.Entities /// /// Class Studio /// - public class Studio : BaseItem, IItemByName + public class Studio : BaseItem, IItemByName, IHasTags { + public List Tags { get; set; } + + public Studio() + { + Tags = new List(); + } + /// /// Gets the user data key. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 2d7c798ad..1933cc716 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -66,6 +66,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security ? null : UserManager.GetUserById(new Guid(auth.UserId)); + if (user == null & !string.IsNullOrWhiteSpace(auth.UserId)) + { + // TODO: Re-enable + //throw new ArgumentException("User with Id " + auth.UserId + " not found"); + } + if (user != null && user.Configuration.IsDisabled) { throw new UnauthorizedAccessException("User account has been disabled."); -- cgit v1.2.3 From 06118307dd95b0834d67f3ae0604e3ffaf04af2a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 17 Jul 2014 18:21:35 -0400 Subject: disable chunked encoding for images --- MediaBrowser.Api/Images/ImageService.cs | 60 +++++++++--- MediaBrowser.Api/Images/ImageWriter.cs | 96 ------------------- MediaBrowser.Api/MediaBrowser.Api.csproj | 3 +- MediaBrowser.Api/Playback/EndlessStreamCopy.cs | 32 ------- .../Progressive/BaseProgressiveStreamingService.cs | 7 +- .../Progressive/ProgressiveStreamWriter.cs | 16 +++- MediaBrowser.Api/Sync/SyncService.cs | 106 +++++++++++++++++++++ MediaBrowser.Common/Net/MimeTypes.cs | 4 + MediaBrowser.Controller/Drawing/IImageProcessor.cs | 7 ++ MediaBrowser.Controller/Net/IHttpResultFactory.cs | 19 +++- MediaBrowser.Controller/Sync/ISyncManager.cs | 14 +++ MediaBrowser.Dlna/DlnaManager.cs | 2 +- .../Profiles/PanasonicVieraProfile.cs | 3 +- .../Profiles/SamsungSmartTvProfile.cs | 2 +- .../Drawing/ImageProcessor.cs | 98 ++++--------------- .../HttpServer/HttpResultFactory.cs | 12 ++- .../Localization/JavaScript/javascript.json | 3 +- .../Localization/Server/server.json | 9 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Sync/SyncManager.cs | 52 ++++++++++ MediaBrowser.ServerApplication/ApplicationHost.cs | 4 + 21 files changed, 311 insertions(+), 239 deletions(-) delete mode 100644 MediaBrowser.Api/Images/ImageWriter.cs delete mode 100644 MediaBrowser.Api/Playback/EndlessStreamCopy.cs create mode 100644 MediaBrowser.Api/Sync/SyncService.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/SyncManager.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index deaefe019..10bc53b74 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -564,25 +564,59 @@ namespace MediaBrowser.Api.Images cacheDuration = TimeSpan.FromDays(365); } - // Avoid implicitly captured closure - var currentItem = item; - var currentRequest = request; - var responseHeaders = new Dictionary { {"transferMode.dlna.org", "Interactive"}, {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} }; - return ToCachedResult(cacheGuid, originalFileImageDateModified, cacheDuration, () => new ImageWriter + return GetImageResult(item, + request, + imageInfo, + supportedImageEnhancers, + contentType, + cacheDuration, + responseHeaders) + .Result; + } + + private async Task GetImageResult(IHasImages item, + ImageRequest request, + ItemImageInfo image, + List enhancers, + string contentType, + TimeSpan? cacheDuration, + IDictionary headers) + { + var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; + + if (request.CropWhitespace.HasValue) + { + cropwhitespace = request.CropWhitespace.Value; + } + + var options = new ImageProcessingOptions { - Item = currentItem, - Request = currentRequest, - Enhancers = supportedImageEnhancers, - Image = imageInfo, - ImageProcessor = _imageProcessor + CropWhiteSpace = cropwhitespace, + Enhancers = enhancers, + Height = request.Height, + ImageIndex = request.Index ?? 0, + Image = image, + Item = item, + MaxHeight = request.MaxHeight, + MaxWidth = request.MaxWidth, + Quality = request.Quality, + Width = request.Width, + OutputFormat = request.Format, + AddPlayedIndicator = request.AddPlayedIndicator, + PercentPlayed = request.PercentPlayed, + UnplayedCount = request.UnplayedCount, + BackgroundColor = request.BackgroundColor + }; - }, contentType, responseHeaders); + var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); + + return ResultFactory.GetStaticFileResult(Request, file, contentType, cacheDuration, FileShare.Read, headers); } private string GetMimeType(ImageOutputFormat format, string path) @@ -603,6 +637,10 @@ namespace MediaBrowser.Api.Images { return Common.Net.MimeTypes.GetMimeType("i.png"); } + if (format == ImageOutputFormat.Webp) + { + return Common.Net.MimeTypes.GetMimeType("i.webp"); + } return Common.Net.MimeTypes.GetMimeType(path); } diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs deleted file mode 100644 index b6638d9df..000000000 --- a/MediaBrowser.Api/Images/ImageWriter.cs +++ /dev/null @@ -1,96 +0,0 @@ -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using ServiceStack.Web; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.Images -{ - /// - /// Class ImageWriter - /// - public class ImageWriter : IStreamWriter, IHasOptions - { - public List Enhancers; - - /// - /// Gets or sets the request. - /// - /// The request. - public ImageRequest Request { get; set; } - /// - /// Gets or sets the item. - /// - /// The item. - public IHasImages Item { get; set; } - /// - /// The original image date modified - /// - public ItemImageInfo Image; - - public IImageProcessor ImageProcessor { get; set; } - - /// - /// The _options - /// - private readonly IDictionary _options = new Dictionary(); - /// - /// Gets the options. - /// - /// The options. - public IDictionary Options - { - get { return _options; } - } - - /// - /// Writes to. - /// - /// The response stream. - public void WriteTo(Stream responseStream) - { - var task = WriteToAsync(responseStream); - - Task.WaitAll(task); - } - - /// - /// Writes to async. - /// - /// The response stream. - /// Task. - private Task WriteToAsync(Stream responseStream) - { - var cropwhitespace = Request.Type == ImageType.Logo || Request.Type == ImageType.Art; - - if (Request.CropWhitespace.HasValue) - { - cropwhitespace = Request.CropWhitespace.Value; - } - - var options = new ImageProcessingOptions - { - CropWhiteSpace = cropwhitespace, - Enhancers = Enhancers, - Height = Request.Height, - ImageIndex = Request.Index ?? 0, - Image = Image, - Item = Item, - MaxHeight = Request.MaxHeight, - MaxWidth = Request.MaxWidth, - Quality = Request.Quality, - Width = Request.Width, - OutputFormat = Request.Format, - AddPlayedIndicator = Request.AddPlayedIndicator, - PercentPlayed = Request.PercentPlayed, - UnplayedCount = Request.UnplayedCount, - BackgroundColor = Request.BackgroundColor - }; - - return ImageProcessor.ProcessImage(options, responseStream); - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 6a1e45e25..f3857ce30 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -85,7 +85,6 @@ - @@ -102,7 +101,6 @@ - @@ -123,6 +121,7 @@ + diff --git a/MediaBrowser.Api/Playback/EndlessStreamCopy.cs b/MediaBrowser.Api/Playback/EndlessStreamCopy.cs deleted file mode 100644 index 40586261f..000000000 --- a/MediaBrowser.Api/Playback/EndlessStreamCopy.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.Playback -{ - public class EndlessStreamCopy - { - public async Task CopyStream(Stream source, Stream target, CancellationToken cancellationToken) - { - long position = 0; - - while (!cancellationToken.IsCancellationRequested) - { - await source.CopyToAsync(target, 81920, cancellationToken).ConfigureAwait(false); - - var fsPosition = source.Position; - - var bytesRead = fsPosition - position; - - //Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path); - - if (bytesRead == 0) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - position = fsPosition; - } - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index dd8960649..80428269b 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { - return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); + return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, null, FileShare.Read, responseHeaders, isHeadRequest); } } @@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Progressive try { - return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); + return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, null, FileShare.Read, responseHeaders, isHeadRequest); } finally { @@ -285,7 +285,8 @@ namespace MediaBrowser.Api.Playback.Progressive state.Dispose(); } - var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem); + var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); + var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job); result.Options["Content-Type"] = contentType; diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 36ae7f100..a4c55443c 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Api.Playback.Progressive private string Path { get; set; } private ILogger Logger { get; set; } private readonly IFileSystem _fileSystem; + private readonly TranscodingJob _job; /// /// The _options @@ -32,11 +33,12 @@ namespace MediaBrowser.Api.Playback.Progressive /// The path. /// The logger. /// The file system. - public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem) + public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job) { Path = path; Logger = logger; _fileSystem = fileSystem; + _job = job; } /// @@ -59,7 +61,8 @@ namespace MediaBrowser.Api.Playback.Progressive { try { - await new ProgressiveFileCopier(_fileSystem).StreamFile(Path, responseStream).ConfigureAwait(false); + await new ProgressiveFileCopier(_fileSystem, _job) + .StreamFile(Path, responseStream).ConfigureAwait(false); } catch { @@ -77,10 +80,12 @@ namespace MediaBrowser.Api.Playback.Progressive public class ProgressiveFileCopier { private readonly IFileSystem _fileSystem; + private readonly TranscodingJob _job; - public ProgressiveFileCopier(IFileSystem fileSystem) + public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job) { _fileSystem = fileSystem; + _job = job; } public async Task StreamFile(string path, Stream outputStream) @@ -102,7 +107,10 @@ namespace MediaBrowser.Api.Playback.Progressive if (bytesRead == 0) { - eofCount++; + if (_job == null || _job.HasExited) + { + eofCount++; + } await Task.Delay(100).ConfigureAwait(false); } else diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs new file mode 100644 index 000000000..73c04edaa --- /dev/null +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -0,0 +1,106 @@ +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Sync; +using ServiceStack; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Sync +{ + [Route("/Sync/Jobs/{Id}", "DELETE", Summary = "Cancels a sync job.")] + public class CancelSyncJob : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + 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 + { + [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + 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> + { + } + + [Authenticated] + public class SyncService : BaseApiService + { + private readonly ISyncManager _syncManager; + + public SyncService(ISyncManager syncManager) + { + _syncManager = syncManager; + } + + public object Get(GetSyncJobs request) + { + var result = _syncManager.GetJobs(new SyncJobQuery + { + + }); + + 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); + + 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); + + Task.WaitAll(task); + } + + public void Delete(CancelSyncSchedule request) + { + var task = _syncManager.CancelSchedule(request.Id); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs index dcac5e7ba..0740bf6d1 100644 --- a/MediaBrowser.Common/Net/MimeTypes.cs +++ b/MediaBrowser.Common/Net/MimeTypes.cs @@ -143,6 +143,10 @@ namespace MediaBrowser.Common.Net { return "image/png"; } + if (ext.Equals(".webp", StringComparison.OrdinalIgnoreCase)) + { + return "image/webp"; + } if (ext.Equals(".ico", StringComparison.OrdinalIgnoreCase)) { return "image/vnd.microsoft.icon"; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 51466c4f9..a0128f111 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -73,6 +73,13 @@ namespace MediaBrowser.Controller.Drawing /// To stream. /// Task. Task ProcessImage(ImageProcessingOptions options, Stream toStream); + + /// + /// Processes the image. + /// + /// The options. + /// Task. + Task ProcessImage(ImageProcessingOptions options); /// /// Gets the enhanced image. diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index f7984c32c..665c1f8d9 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -80,9 +80,13 @@ namespace MediaBrowser.Controller.Net /// The response headers. /// if set to true [is head request]. /// System.Object. - object GetStaticResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, - TimeSpan? cacheDuration, string contentType, Func> factoryFn, - IDictionary responseHeaders = null, bool isHeadRequest = false); + object GetStaticResult(IRequest requestContext, + Guid cacheKey, + DateTime? lastDateModified, + TimeSpan? cacheDuration, + string contentType, Func> factoryFn, + IDictionary responseHeaders = null, + bool isHeadRequest = false); /// /// Gets the static file result. @@ -101,11 +105,18 @@ namespace MediaBrowser.Controller.Net /// The request context. /// The path. /// Type of the content. + /// The cache curation. /// The file share. /// The response headers. /// if set to true [is head request]. /// System.Object. - object GetStaticFileResult(IRequest requestContext, string path, string contentType, FileShare fileShare = FileShare.Read, IDictionary responseHeaders = null, bool isHeadRequest = false); + object GetStaticFileResult(IRequest requestContext, + string path, + string contentType, + TimeSpan? cacheCuration = null, + FileShare fileShare = FileShare.Read, + IDictionary responseHeaders = null, + bool isHeadRequest = false); /// /// Gets the optimized serialized result using cache. diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index fc92718ca..63a5d8a7c 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -33,6 +33,20 @@ namespace MediaBrowser.Controller.Sync /// 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. /// diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 048a7a0cf..333521bb5 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Dlna _logger = logger; _jsonSerializer = jsonSerializer; - DumpProfiles(); + //DumpProfiles(); } public IEnumerable GetProfiles() diff --git a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs index b9c4b25bc..d12b3598c 100644 --- a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Dlna; using System.Xml.Serialization; -using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Dlna.Profiles { @@ -198,7 +197,7 @@ namespace MediaBrowser.Dlna.Profiles { new SubtitleProfile { - Format = SubtitleFormat.SRT + Format = "srt" } }; } diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs index 4611edc18..b90c906fb 100644 --- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -343,7 +343,7 @@ namespace MediaBrowser.Dlna.Profiles { new SubtitleProfile { - Format = SubtitleFormat.SMI + Format = "smi" } }; } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 86bd6b731..09071cbf9 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -118,14 +118,19 @@ namespace MediaBrowser.Server.Implementations.Drawing public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) { - if (options == null) + var file = await ProcessImage(options).ConfigureAwait(false); + + using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { - throw new ArgumentNullException("options"); + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } + } - if (toStream == null) + public async Task ProcessImage(ImageProcessingOptions options) + { + if (options == null) { - throw new ArgumentNullException("toStream"); + throw new ArgumentNullException("options"); } var originalImagePath = options.Image.Path; @@ -133,11 +138,7 @@ namespace MediaBrowser.Server.Implementations.Drawing if (options.HasDefaultOptions() && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default - using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } + return originalImagePath; } var dateModified = options.Image.DateModified; @@ -166,30 +167,13 @@ namespace MediaBrowser.Server.Implementations.Drawing if (options.HasDefaultOptionsWithoutSize() && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old - using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } + return originalImagePath; } var quality = options.Quality ?? 90; var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); - try - { - using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } - } - catch (IOException) - { - // Cache file doesn't exist or is currently being written to - } - var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); @@ -197,17 +181,12 @@ namespace MediaBrowser.Server.Implementations.Drawing // Check again in case of lock contention try { - using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) + if (File.Exists(cacheFilePath)) { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); semaphore.Release(); - return; + return cacheFilePath; } } - catch (IOException) - { - // Cache file doesn't exist or is currently being written to - } catch { semaphore.Release(); @@ -218,37 +197,6 @@ namespace MediaBrowser.Server.Implementations.Drawing { var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue; - //if (!hasPostProcessing) - //{ - // using (var outputStream = await _mediaEncoder.EncodeImage(new ImageEncodingOptions - // { - // InputPath = originalImagePath, - // MaxHeight = options.MaxHeight, - // MaxWidth = options.MaxWidth, - // Height = options.Height, - // Width = options.Width, - // Quality = options.Quality, - // Format = options.OutputFormat == ImageOutputFormat.Original ? Path.GetExtension(originalImagePath).TrimStart('.') : options.OutputFormat.ToString().ToLower() - - // }, CancellationToken.None).ConfigureAwait(false)) - // { - // using (var outputMemoryStream = new MemoryStream()) - // { - // // Save to the memory stream - // await outputStream.CopyToAsync(outputMemoryStream).ConfigureAwait(false); - - // var bytes = outputMemoryStream.ToArray(); - - // await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - - // // kick off a task to cache the result - // await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); - // } - - // return; - // } - //} - using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { // Copy to memory stream to avoid Image locking file @@ -289,18 +237,16 @@ namespace MediaBrowser.Server.Implementations.Drawing var outputFormat = GetOutputFormat(originalImage, options.OutputFormat); - using (var outputMemoryStream = new MemoryStream()) + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); + + // Save to the cache location + using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false)) { // Save to the memory stream - thumbnail.Save(outputFormat, outputMemoryStream, quality); - - var bytes = outputMemoryStream.ToArray(); - - await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - - // kick off a task to cache the result - await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); + thumbnail.Save(outputFormat, cacheFileStream, quality); } + + return cacheFilePath; } } @@ -324,9 +270,7 @@ namespace MediaBrowser.Server.Implementations.Drawing { try { - var parentPath = Path.GetDirectoryName(cacheFilePath); - - Directory.CreateDirectory(parentPath); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); // Save to the cache location using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 8831d635c..6a60e5ea6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -306,11 +306,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw new ArgumentNullException("path"); } - return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), fileShare, responseHeaders, isHeadRequest); + return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), null, fileShare, responseHeaders, isHeadRequest); } - public object GetStaticFileResult(IRequest requestContext, string path, string contentType, - FileShare fileShare = FileShare.Read, IDictionary responseHeaders = null, + public object GetStaticFileResult(IRequest requestContext, + string path, + string contentType, + TimeSpan? cacheCuration = null, + FileShare fileShare = FileShare.Read, + IDictionary responseHeaders = null, bool isHeadRequest = false) { if (string.IsNullOrEmpty(path)) @@ -327,7 +331,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var cacheKey = path + dateModified.Ticks; - return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); + return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, cacheCuration, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); } /// diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index d88e2d6ab..187bcb59f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -319,5 +319,6 @@ "ButtonAudioTracks": "Audio Tracks", "ButtonSubtitles": "Subtitles", "ButtonScenes": "Scenes", - "ButtonQuality": "Quality" + "ButtonQuality": "Quality", + "HeaderNotifications": "Notifications" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index e203a6d9d..741bc52ca 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -882,5 +882,12 @@ "LabelAppName": "App name", "LabelAppNameExample": "Example: Sickbeard, NzbDrone", "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser.", - "ButtonEnterSupporterKey": "Enter supporter key" + "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 diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 376a95e04..1bb789484 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -261,6 +261,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs new file mode 100644 index 000000000..373b30a41 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class SyncManager : ISyncManager + { + public Task> CreateJob(SyncJobRequest request) + { + throw new NotImplementedException(); + } + + public Task CreateSchedule(SyncScheduleRequest request) + { + throw new NotImplementedException(); + } + + public QueryResult GetJobs(SyncJobQuery query) + { + throw new NotImplementedException(); + } + + public QueryResult GetSchedules(SyncScheduleQuery query) + { + throw new NotImplementedException(); + } + + public Task CancelJob(string id) + { + throw new NotImplementedException(); + } + + public Task CancelSchedule(string id) + { + throw new NotImplementedException(); + } + + public SyncJob GetJob(string id) + { + throw new NotImplementedException(); + } + + public SyncSchedule GetSchedule(string id) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 1e248ab57..60d1c92b4 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -33,6 +33,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Controller.Sync; using MediaBrowser.Controller.Themes; using MediaBrowser.Dlna; using MediaBrowser.Dlna.ConnectionManager; @@ -69,6 +70,7 @@ using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Implementations.Security; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; +using MediaBrowser.Server.Implementations.Sync; using MediaBrowser.Server.Implementations.Themes; using MediaBrowser.Server.Implementations.WebSocket; using MediaBrowser.ServerApplication.EntryPoints; @@ -629,6 +631,8 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + RegisterSingleInstance(new SyncManager()); + var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); -- cgit v1.2.3 From ea559a6e274c2067cda780ba81bc5237c9e2ebf7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 18 Jul 2014 18:14:59 -0400 Subject: create http listener abstraction --- MediaBrowser.Common/Net/IServerManager.cs | 3 +- MediaBrowser.Controller/Net/IHttpServer.cs | 6 - .../Configuration/ServerConfiguration.cs | 7 - .../HttpServer/HttpListenerHost.cs | 226 +++-------------- .../HttpServer/IHttpListener.cs | 42 +++ .../HttpServer/NetListener/HttpListenerServer.cs | 281 +++++++++++++++++++++ .../MediaBrowser.Server.Implementations.csproj | 5 + .../ServerManager/ServerManager.cs | 7 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 4 +- 9 files changed, 367 insertions(+), 214 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Common/Net/IServerManager.cs b/MediaBrowser.Common/Net/IServerManager.cs index f81c99ac6..7c14f98ce 100644 --- a/MediaBrowser.Common/Net/IServerManager.cs +++ b/MediaBrowser.Common/Net/IServerManager.cs @@ -26,8 +26,7 @@ namespace MediaBrowser.Common.Net /// Starts this instance. /// /// The URL prefixes. - /// if set to true [enable HTTP logging]. - void Start(IEnumerable urlPrefixes, bool enableHttpLogging); + void Start(IEnumerable urlPrefixes); /// /// Starts the web socket server. diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 20f07c74d..9a3cc6cb5 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -38,12 +38,6 @@ namespace MediaBrowser.Controller.Net /// void Stop(); - /// - /// Gets or sets a value indicating whether [enable HTTP request logging]. - /// - /// true if [enable HTTP request logging]; otherwise, false. - bool EnableHttpRequestLogging { get; set; } - /// /// Occurs when [web socket connected]. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 064ac234f..904c7bcdd 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -22,12 +22,6 @@ namespace MediaBrowser.Model.Configuration /// The weather unit. public WeatherUnits WeatherUnit { get; set; } - /// - /// Gets or sets a value indicating whether [enable HTTP level logging]. - /// - /// true if [enable HTTP level logging]; otherwise, false. - public bool EnableHttpLevelLogging { get; set; } - /// /// Gets or sets a value indicating whether [enable u pn p]. /// @@ -223,7 +217,6 @@ namespace MediaBrowser.Model.Configuration ImageSavingConvention = ImageSavingConvention.Compatible; HttpServerPortNumber = 8096; LegacyWebSocketPortNumber = 8945; - EnableHttpLevelLogging = true; EnableDashboardResponseCaching = true; EnableAutomaticRestart = true; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 340149182..68b69cdf0 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.HttpServer.NetListener; using ServiceStack; using ServiceStack.Api.Swagger; using ServiceStack.Host; @@ -34,14 +35,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly List _restServices = new List(); - private HttpListener Listener { get; set; } - protected bool IsStarted = false; + private IHttpListener _listener; - private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); private readonly SmartThreadPool _threadPoolManager; + private const int IdleTimeout = 300; - private const int IdleTimeout = 300; - private readonly ContainerAdapter _containerAdapter; private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -53,7 +51,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The local end points. public IEnumerable LocalEndPoints { - get { return _localEndPoints.Keys.ToList(); } + get { return _listener == null ? new List() : _listener.LocalEndPoints; } } public HttpListenerHost(IApplicationHost applicationHost, ILogManager logManager, string serviceName, string handlerPath, string defaultRedirectPath, params Assembly[] assembliesWithServices) @@ -148,120 +146,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer public override ServiceStackHost Start(string listeningAtUrlBase) { - StartListener(Listen); + StartListener(); return this; } /// /// Starts the Web Service /// - private void StartListener(WaitCallback listenCallback) + private void StartListener() { - // *** Already running - just leave it in place - if (IsStarted) - return; - - if (Listener == null) - Listener = new HttpListener(); - HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - foreach (var prefix in UrlPrefixes) - { - _logger.Info("Adding HttpListener prefix " + prefix); - Listener.Prefixes.Add(prefix); - } - - IsStarted = true; - _logger.Info("Starting HttpListner"); - Listener.Start(); - _logger.Info("HttpListener started"); - - ThreadPool.QueueUserWorkItem(listenCallback); - } - - private bool IsListening - { - get { return this.IsStarted && this.Listener != null && this.Listener.IsListening; } - } - - // Loop here to begin processing of new requests. - private void Listen(object state) - { - while (IsListening) - { - if (Listener == null) return; - - try - { - Listener.BeginGetContext(ListenerCallback, Listener); - _listenForNextRequest.WaitOne(); - } - catch (Exception ex) - { - _logger.Error("Listen()", ex); - return; - } - if (Listener == null) return; - } - } - - // Handle the processing of a request in here. - private void ListenerCallback(IAsyncResult asyncResult) - { - var listener = asyncResult.AsyncState as HttpListener; - HttpListenerContext context; - - if (listener == null) return; - var isListening = listener.IsListening; - - try - { - if (!isListening) - { - _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return; - } - // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, - // blocks until there is a request to be processed or some type of data is available. - context = listener.EndGetContext(asyncResult); - } - catch (Exception ex) + _listener = new HttpListenerServer(_logger, _threadPoolManager) { - // You will get an exception when httpListener.Stop() is called - // because there will be a thread stopped waiting on the .EndGetContext() - // method, and again, that is just the way most Begin/End asynchronous - // methods of the .NET Framework work. - var errMsg = ex + ": " + IsListening; - _logger.Warn(errMsg); - return; - } - finally - { - // Once we know we have a request (or exception), we signal the other thread - // so that it calls the BeginGetContext() (or possibly exits if we're not - // listening any more) method to start handling the next incoming request - // while we continue to process this request on a different thread. - _listenForNextRequest.Set(); - } + WebSocketHandler = WebSocketHandler, + ErrorHandler = ErrorHandler, + RequestHandler = RequestHandler + }; - _threadPoolManager.QueueWorkItem(() => InitTask(context)); + _listener.Start(UrlPrefixes); } - public virtual void InitTask(HttpListenerContext context) + private void WebSocketHandler(WebSocketConnectEventArgs args) { - try - { - var task = this.ProcessRequestAsync(context); - task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); - - if (task.Status == TaskStatus.Created) - { - task.RunSynchronously(); - } - } - catch (Exception ex) + if (WebSocketConnected != null) { - HandleError(ex, context); + WebSocketConnected(this, args); } } @@ -280,43 +190,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer _localEndPoints.GetOrAdd(address, address); } - if (EnableHttpRequestLogging) - { - LoggerUtils.LogRequest(_logger, request); - } - } - - /// - /// Processes the web socket request. - /// - /// The CTX. - /// Task. - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { -#if !__MonoCS__ - try - { - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - if (WebSocketConnected != null) - { - WebSocketConnected(this, new WebSocketConnectEventArgs { WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), Endpoint = ctx.Request.RemoteEndPoint.ToString() }); - } - } - catch (Exception ex) - { - _logger.ErrorException("AcceptWebSocketAsync error", ex); - ctx.Response.StatusCode = 500; - ctx.Response.Close(); - } -#endif + LoggerUtils.LogRequest(_logger, request); } - private void HandleError(Exception ex, HttpListenerContext context) + private void ErrorHandler(Exception ex, IRequest httpReq) { try { - var operationName = context.Request.GetOperationName(); - var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; if (httpRes.IsClosed) @@ -366,84 +246,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } - private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName) - { - var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); - req.RequestAttributes = req.GetAttributes(); - - return req; - } - /// /// Shut down the Web Service /// public void Stop() { - if (Listener != null) + if (_listener != null) { - foreach (var prefix in UrlPrefixes) - { - Listener.Prefixes.Remove(prefix); - } - - Listener.Close(); + _listener.Stop(); } } /// /// Overridable method that can be used to implement a custom hnandler /// - /// - protected Task ProcessRequestAsync(HttpListenerContext context) + /// The HTTP req. + /// Task. + protected Task RequestHandler(IHttpRequest httpReq, Uri url) { - var request = context.Request; - - LogHttpRequest(request); + var date = DateTime.Now; - if (request.IsWebSocketRequest) - { - return ProcessWebSocketRequest(context); - } + var httpRes = httpReq.Response; - var localPath = request.Url.LocalPath; + var operationName = httpReq.OperationName; + var localPath = url.LocalPath; if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect(DefaultRedirectPath); - context.Response.Close(); + httpRes.RedirectToUrl(DefaultRedirectPath); return Task.FromResult(true); } if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); + httpRes.RedirectToUrl(HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); + httpRes.RedirectToUrl(HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } if (string.IsNullOrEmpty(localPath)) { - context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); - context.Response.Close(); + httpRes.RedirectToUrl("/" + HandlerPath + "/" + DefaultRedirectPath); return Task.FromResult(true); } - var date = DateTime.Now; - - if (string.IsNullOrEmpty(context.Request.RawUrl)) - return ((object)null).AsTaskResult(); - - var operationName = context.Request.GetOperationName(); - - var httpReq = GetRequest(context, operationName); - var httpRes = httpReq.Response; var handler = HttpHandlerFactory.GetHandler(httpReq); - var url = request.Url.ToString(); var remoteIp = httpReq.RemoteIp; var serviceStackHandler = handler as IServiceStackHandler; @@ -461,15 +311,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer //Matches Exceptions handled in HttpListenerBase.InitTask() var statusCode = httpRes.StatusCode; + var urlString = url.ToString(); task.ContinueWith(x => { var duration = DateTime.Now - date; - if (EnableHttpRequestLogging) - { - LoggerUtils.LogResponse(_logger, statusCode, url, remoteIp, duration); - } + LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration); }, TaskContinuationOptions.None); @@ -480,12 +328,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer .AsTaskException(); } - /// - /// Gets or sets a value indicating whether [enable HTTP request logging]. - /// - /// true if [enable HTTP request logging]; otherwise, false. - public bool EnableHttpRequestLogging { get; set; } - /// /// Adds the rest handlers. /// @@ -530,8 +372,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (disposing) { - _threadPoolManager.Dispose(); - + _threadPoolManager.Dispose(); + Stop(); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs new file mode 100644 index 000000000..1d80a263c --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using ServiceStack.Web; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.HttpServer +{ + public interface IHttpListener : IDisposable + { + IEnumerable LocalEndPoints { get; } + + /// + /// Gets or sets the error handler. + /// + /// The error handler. + Action ErrorHandler { get; set; } + + /// + /// Gets or sets the request handler. + /// + /// The request handler. + Func RequestHandler { get; set; } + + /// + /// Gets or sets the web socket handler. + /// + /// The web socket handler. + Action WebSocketHandler { get; set; } + + /// + /// Starts this instance. + /// + /// The URL prefixes. + void Start(IEnumerable urlPrefixes); + + /// + /// Stops this instance. + /// + void Stop(); + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs new file mode 100644 index 000000000..51f0554d7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs @@ -0,0 +1,281 @@ +using Amib.Threading; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using ServiceStack; +using ServiceStack.Host.HttpListener; +using ServiceStack.Web; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.HttpServer.NetListener +{ + public class HttpListenerServer : IHttpListener + { + 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; } + public System.Func RequestHandler { get; set; } + + private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public HttpListenerServer(ILogger logger, SmartThreadPool threadPoolManager) + { + _logger = logger; + + _threadPoolManager = threadPoolManager; + } + + /// + /// Gets the local end points. + /// + /// The local end points. + public IEnumerable LocalEndPoints + { + get { return _localEndPoints.Keys.ToList(); } + } + + private List UrlPrefixes { get; set; } + + public void Start(IEnumerable urlPrefixes) + { + UrlPrefixes = urlPrefixes.ToList(); + + if (_listener == null) + _listener = new System.Net.HttpListener(); + + //HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); + + foreach (var prefix in UrlPrefixes) + { + _logger.Info("Adding HttpListener prefix " + prefix); + _listener.Prefixes.Add(prefix); + } + + _listener.Start(); + + ThreadPool.QueueUserWorkItem(Listen); + } + + private bool IsListening + { + get { return _listener != null && _listener.IsListening; } + } + + // Loop here to begin processing of new requests. + private void Listen(object state) + { + while (IsListening) + { + if (_listener == null) return; + + try + { + _listener.BeginGetContext(ListenerCallback, _listener); + _listenForNextRequest.WaitOne(); + } + catch (Exception ex) + { + _logger.Error("Listen()", ex); + return; + } + if (_listener == null) return; + } + } + + // Handle the processing of a request in here. + private void ListenerCallback(IAsyncResult asyncResult) + { + var listener = asyncResult.AsyncState as HttpListener; + HttpListenerContext context; + + if (listener == null) return; + var isListening = listener.IsListening; + + try + { + if (!isListening) + { + _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return; + } + // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, + // blocks until there is a request to be processed or some type of data is available. + context = listener.EndGetContext(asyncResult); + } + catch (Exception ex) + { + // You will get an exception when httpListener.Stop() is called + // because there will be a thread stopped waiting on the .EndGetContext() + // method, and again, that is just the way most Begin/End asynchronous + // methods of the .NET Framework work. + var errMsg = ex + ": " + IsListening; + _logger.Warn(errMsg); + return; + } + finally + { + // Once we know we have a request (or exception), we signal the other thread + // so that it calls the BeginGetContext() (or possibly exits if we're not + // listening any more) method to start handling the next incoming request + // while we continue to process this request on a different thread. + _listenForNextRequest.Set(); + } + + _threadPoolManager.QueueWorkItem(() => InitTask(context)); + } + + public virtual void InitTask(HttpListenerContext context) + { + try + { + var task = this.ProcessRequestAsync(context); + task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); + + if (task.Status == TaskStatus.Created) + { + task.RunSynchronously(); + } + } + catch (Exception ex) + { + HandleError(ex, context); + } + } + + protected Task ProcessRequestAsync(HttpListenerContext context) + { + var request = context.Request; + + LogHttpRequest(request); + + if (request.IsWebSocketRequest) + { + return ProcessWebSocketRequest(context); + } + + if (string.IsNullOrEmpty(context.Request.RawUrl)) + return ((object)null).AsTaskResult(); + + var operationName = context.Request.GetOperationName(); + + var httpReq = GetRequest(context, operationName); + + return RequestHandler(httpReq, request.Url); + } + + /// + /// Processes the web socket request. + /// + /// The CTX. + /// Task. + private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + { +#if !__MonoCS__ + try + { + var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + + if (WebSocketHandler != null) + { + WebSocketHandler(new WebSocketConnectEventArgs + { + WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), + Endpoint = ctx.Request.RemoteEndPoint.ToString() + }); + } + } + catch (Exception ex) + { + _logger.ErrorException("AcceptWebSocketAsync error", ex); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } +#endif + } + + private void HandleError(Exception ex, HttpListenerContext context) + { + var operationName = context.Request.GetOperationName(); + var httpReq = GetRequest(context, operationName); + + if (ErrorHandler != null) + { + ErrorHandler(ex, httpReq); + } + } + + private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName) + { + var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); + req.RequestAttributes = req.GetAttributes(); + + return req; + } + + /// + /// Logs the HTTP request. + /// + /// The request. + private void LogHttpRequest(HttpListenerRequest request) + { + var endpoint = request.LocalEndPoint; + + if (endpoint != null) + { + var address = endpoint.ToString(); + + _localEndPoints.GetOrAdd(address, address); + } + + LoggerUtils.LogRequest(_logger, request); + } + + public void Stop() + { + if (_listener != null) + { + foreach (var prefix in UrlPrefixes) + { + _listener.Prefixes.Remove(prefix); + } + + _listener.Close(); + } + } + + public void Dispose() + { + Dispose(true); + } + + private bool _disposed; + private readonly object _disposeLock = new object(); + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + lock (_disposeLock) + { + if (_disposed) return; + + if (disposing) + { + _threadPoolManager.Dispose(); + + Stop(); + } + + //release unmanaged resources here... + _disposed = true; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8d4eec29e..c24e9574f 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -96,6 +96,9 @@ ..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll + + ..\ThirdParty\WebsocketSharp\websocket-sharp.dll + @@ -134,6 +137,8 @@ + + diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index e66b87b0c..5931d7718 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -123,9 +123,9 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// /// Starts this instance. /// - public void Start(IEnumerable urlPrefixes, bool enableHttpLogging) + public void Start(IEnumerable urlPrefixes) { - ReloadHttpServer(urlPrefixes, enableHttpLogging); + ReloadHttpServer(urlPrefixes); } public void StartWebSocketServer() @@ -152,14 +152,13 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// /// Restarts the Http Server, or starts it if not currently running /// - private void ReloadHttpServer(IEnumerable urlPrefixes, bool enableHttpLogging) + private void ReloadHttpServer(IEnumerable urlPrefixes) { _logger.Info("Loading Http Server"); try { HttpServer = _applicationHost.Resolve(); - HttpServer.EnableHttpRequestLogging = enableHttpLogging; HttpServer.StartServer(urlPrefixes); } catch (SocketException ex) diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 60d1c92b4..da7f8631b 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -851,7 +851,7 @@ namespace MediaBrowser.ServerApplication { try { - ServerManager.Start(HttpServerUrlPrefixes, ServerConfigurationManager.Configuration.EnableHttpLevelLogging); + ServerManager.Start(HttpServerUrlPrefixes); } catch (Exception ex) { @@ -881,8 +881,6 @@ namespace MediaBrowser.ServerApplication { base.OnConfigurationUpdated(sender, e); - HttpServer.EnableHttpRequestLogging = ServerConfigurationManager.Configuration.EnableHttpLevelLogging; - if (!HttpServer.UrlPrefixes.SequenceEqual(HttpServerUrlPrefixes, StringComparer.OrdinalIgnoreCase)) { NotifyPendingRestart(); -- cgit v1.2.3 From bc657237aa4c541fe0079fcbb7616dbe87bbf0a7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 18 Jul 2014 21:28:40 -0400 Subject: consolidate web socket onto one port --- MediaBrowser.Common/MediaBrowser.Common.csproj | 1 - MediaBrowser.Common/Net/IServerManager.cs | 11 - MediaBrowser.Common/Net/IWebSocketServer.cs | 32 - MediaBrowser.Controller/Net/IHttpServer.cs | 6 - .../HttpServer/HttpListenerHost.cs | 44 +- .../HttpServer/LoggerUtils.cs | 18 - .../HttpServer/NetListener/HttpListenerServer.cs | 27 +- .../HttpServer/ResponseFilter.cs | 25 +- .../HttpServer/SocketSharp/Extensions.cs | 28 + .../HttpServer/SocketSharp/RequestMono.cs | 918 +++++++++++++++++++++ .../HttpServer/SocketSharp/SharpWebSocket.cs | 157 ++++ .../SocketSharp/WebSocketSharpListener.cs | 192 +++++ .../SocketSharp/WebSocketSharpRequest.cs | 402 +++++++++ .../SocketSharp/WebSocketSharpResponse.cs | 144 ++++ .../MediaBrowser.Server.Implementations.csproj | 11 +- .../ServerManager/ServerManager.cs | 52 +- .../WebSocket/AlchemyServer.cs | 165 ---- .../WebSocket/AlchemyWebSocket.cs | 134 --- .../packages.config | 1 - MediaBrowser.ServerApplication/ApplicationHost.cs | 12 +- 20 files changed, 1901 insertions(+), 479 deletions(-) delete mode 100644 MediaBrowser.Common/Net/IWebSocketServer.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs delete mode 100644 MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs delete mode 100644 MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 9d5984317..f1f8ebca4 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -78,7 +78,6 @@ - diff --git a/MediaBrowser.Common/Net/IServerManager.cs b/MediaBrowser.Common/Net/IServerManager.cs index 7c14f98ce..f6ac0ab68 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 a value indicating whether [supports web socket]. - /// - /// true if [supports web socket]; otherwise, false. - bool SupportsNativeWebSocket { get; } - /// /// Gets the web socket port number. /// @@ -28,11 +22,6 @@ namespace MediaBrowser.Common.Net /// The URL prefixes. void Start(IEnumerable urlPrefixes); - /// - /// Starts the web socket server. - /// - void StartWebSocketServer(); - /// /// Sends a message to all clients currently connected via a web socket /// diff --git a/MediaBrowser.Common/Net/IWebSocketServer.cs b/MediaBrowser.Common/Net/IWebSocketServer.cs deleted file mode 100644 index 187e03e09..000000000 --- a/MediaBrowser.Common/Net/IWebSocketServer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace MediaBrowser.Common.Net -{ - /// - /// Interface IWebSocketServer - /// - public interface IWebSocketServer : IDisposable - { - /// - /// Starts the specified port number. - /// - /// The port number. - void Start(int portNumber); - - /// - /// Stops this instance. - /// - void Stop(); - - /// - /// Occurs when [web socket connected]. - /// - event EventHandler WebSocketConnected; - - /// - /// Gets the port. - /// - /// The port. - int Port { get; } - } -} diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 9a3cc6cb5..5b179d479 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -21,12 +21,6 @@ namespace MediaBrowser.Controller.Net /// The URL prefixes. void StartServer(IEnumerable urlPrefixes); - /// - /// Gets a value indicating whether [supports web sockets]. - /// - /// true if [supports web sockets]; otherwise, false. - bool SupportsWebSockets { get; } - /// /// Gets the local end points. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 68b69cdf0..62a43751d 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -6,6 +6,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.HttpServer.NetListener; +using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; using ServiceStack; using ServiceStack.Api.Swagger; using ServiceStack.Host; @@ -18,7 +19,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -42,7 +42,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly ContainerAdapter _containerAdapter; - private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); public event EventHandler WebSocketConnected; /// @@ -157,12 +156,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer { HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - _listener = new HttpListenerServer(_logger, _threadPoolManager) - { - WebSocketHandler = WebSocketHandler, - ErrorHandler = ErrorHandler, - RequestHandler = RequestHandler - }; + _listener = NativeWebSocket.IsSupported + ? _listener = new HttpListenerServer(_logger, _threadPoolManager) + : _listener = new WebSocketSharpListener(_logger, _threadPoolManager); + + _listener.WebSocketHandler = WebSocketHandler; + _listener.ErrorHandler = ErrorHandler; + _listener.RequestHandler = RequestHandler; _listener.Start(UrlPrefixes); } @@ -175,24 +175,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } - /// - /// Logs the HTTP request. - /// - /// The request. - private void LogHttpRequest(HttpListenerRequest request) - { - var endpoint = request.LocalEndPoint; - - if (endpoint != null) - { - var address = endpoint.ToString(); - - _localEndPoints.GetOrAdd(address, address); - } - - LoggerUtils.LogRequest(_logger, request); - } - private void ErrorHandler(Exception ex, IRequest httpReq) { try @@ -261,6 +243,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// Overridable method that can be used to implement a custom hnandler /// /// The HTTP req. + /// The URL. /// Task. protected Task RequestHandler(IHttpRequest httpReq, Uri url) { @@ -310,17 +293,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); //Matches Exceptions handled in HttpListenerBase.InitTask() - var statusCode = httpRes.StatusCode; var urlString = url.ToString(); task.ContinueWith(x => { + var statusCode = httpRes.StatusCode; + var duration = DateTime.Now - date; LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration); }, TaskContinuationOptions.None); - return task; } @@ -393,10 +376,5 @@ namespace MediaBrowser.Server.Implementations.HttpServer UrlPrefixes = urlPrefixes.ToList(); Start(UrlPrefixes.First()); } - - public bool SupportsWebSockets - { - get { return NativeWebSocket.IsSupported; } - } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs index 19d2f9c45..955c4ed2d 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs @@ -7,24 +7,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { public static class LoggerUtils { - /// - /// Logs the request. - /// - /// The logger. - /// The request. - public static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var log = new StringBuilder(); - - //var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); - - //log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); - - var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; - - logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log); - } - /// /// Logs the response. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs index 51f0554d7..7f766129e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs @@ -1,4 +1,5 @@ -using Amib.Threading; +using System.Text; +using Amib.Threading; using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; using ServiceStack; @@ -132,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener _threadPoolManager.QueueWorkItem(() => InitTask(context)); } - public virtual void InitTask(HttpListenerContext context) + private void InitTask(HttpListenerContext context) { try { @@ -150,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener } } - protected Task ProcessRequestAsync(HttpListenerContext context) + private Task ProcessRequestAsync(HttpListenerContext context) { var request = context.Request; @@ -235,7 +236,25 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener _localEndPoints.GetOrAdd(address, address); } - LoggerUtils.LogRequest(_logger, request); + LogRequest(_logger, request); + } + + /// + /// Logs the request. + /// + /// The logger. + /// The request. + private static void LogRequest(ILogger logger, HttpListenerRequest request) + { + var log = new StringBuilder(); + + //var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); + + //log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); + + var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; + + logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log); } public void Stop() diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs index ac1621709..e0a5764d5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,4 +1,5 @@ using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; using ServiceStack; using ServiceStack.Web; using System; @@ -66,13 +67,23 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (length > 0) { - var response = (HttpListenerResponse)res.OriginalResponse; - - response.ContentLength64 = length; - - // Disable chunked encoding. Technically this is only needed when using Content-Range, but - // anytime we know the content length there's no need for it - response.SendChunked = false; + res.SetContentLength(length); + + var listenerResponse = res.OriginalResponse as HttpListenerResponse; + + if (listenerResponse != null) + { + // Disable chunked encoding. Technically this is only needed when using Content-Range, but + // anytime we know the content length there's no need for it + listenerResponse.SendChunked = false; + return; + } + + var sharpResponse = res as WebSocketSharpResponse; + if (sharpResponse != null) + { + sharpResponse.SendChunked = false; + } } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs new file mode 100644 index 000000000..63d57b6be --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs @@ -0,0 +1,28 @@ +using System; +using MediaBrowser.Model.Logging; +using WebSocketSharp.Net; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public static class Extensions + { + public static string GetOperationName(this HttpListenerRequest request) + { + return request.Url.Segments[request.Url.Segments.Length - 1]; + } + + public static void CloseOutputStream(this HttpListenerResponse response, ILogger logger) + { + try + { + response.OutputStream.Flush(); + response.OutputStream.Close(); + response.Close(); + } + catch (Exception ex) + { + logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs new file mode 100644 index 000000000..226d97b3c --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs @@ -0,0 +1,918 @@ +using System; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Text; +using System.Web; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + static internal string GetParameter(string header, string attr) + { + int ap = header.IndexOf(attr); + if (ap == -1) + return null; + + ap += attr.Length; + if (ap >= header.Length) + return null; + + char ending = header[ap]; + if (ending != '"') + ending = ' '; + + int end = header.IndexOf(ending, ap + 1); + if (end == -1) + return (ending == '"') ? null : header.Substring(ap); + + return header.Substring(ap + 1, end - ap - 1); + } + + void LoadMultiPart() + { + string boundary = GetParameter(ContentType, "; boundary="); + if (boundary == null) + return; + + var input = GetSubStream(InputStream); + + //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request + //Not ending with \r\n? + var ms = new MemoryStream(32 * 1024); + input.CopyTo(ms); + input = ms; + ms.WriteByte((byte)'\r'); + ms.WriteByte((byte)'\n'); + + input.Position = 0; + + //Uncomment to debug + //var content = new StreamReader(ms).ReadToEnd(); + //Console.WriteLine(boundary + "::" + content); + //input.Position = 0; + + var multi_part = new HttpMultipart(input, boundary, ContentEncoding); + + HttpMultipart.Element e; + while ((e = multi_part.ReadNextElement()) != null) + { + if (e.Filename == null) + { + byte[] copy = new byte[e.Length]; + + input.Position = e.Start; + input.Read(copy, 0, (int)e.Length); + + form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy)); + } + else + { + // + // We use a substream, as in 2.x we will support large uploads streamed to disk, + // + HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); + files.AddFile(e.Name, sub); + } + } + EndSubStream(input); + } + + public NameValueCollection Form + { + get + { + if (form == null) + { + form = new WebROCollection(); + files = new HttpFileCollection(); + + if (IsContentType("multipart/form-data", true)) + LoadMultiPart(); + else if ( + IsContentType("application/x-www-form-urlencoded", true)) + LoadWwwForm(); + + form.Protect(); + } + +#if NET_4_0 + if (validateRequestNewMode && !checked_form) { + // Setting this before calling the validator prevents + // possible endless recursion + checked_form = true; + ValidateNameValueCollection ("Form", query_string_nvc, RequestValidationSource.Form); + } else +#endif + if (validate_form && !checked_form) + { + checked_form = true; + ValidateNameValueCollection("Form", form); + } + + return form; + } + } + + + protected bool validate_cookies, validate_query_string, validate_form; + protected bool checked_cookies, checked_query_string, checked_form; + + static void ThrowValidationException(string name, string key, string value) + { + string v = "\"" + value + "\""; + if (v.Length > 20) + v = v.Substring(0, 16) + "...\""; + + string msg = String.Format("A potentially dangerous Request.{0} value was " + + "detected from the client ({1}={2}).", name, key, v); + + throw new HttpRequestValidationException(msg); + } + + static void ValidateNameValueCollection(string name, NameValueCollection coll) + { + if (coll == null) + return; + + foreach (string key in coll.Keys) + { + string val = coll[key]; + if (val != null && val.Length > 0 && IsInvalidString(val)) + ThrowValidationException(name, key, val); + } + } + + internal static bool IsInvalidString(string val) + { + int validationFailureIndex; + + return IsInvalidString(val, out validationFailureIndex); + } + + internal static bool IsInvalidString(string val, out int validationFailureIndex) + { + validationFailureIndex = 0; + + int len = val.Length; + if (len < 2) + return false; + + char current = val[0]; + for (int idx = 1; idx < len; idx++) + { + char next = val[idx]; + // See http://secunia.com/advisories/14325 + if (current == '<' || current == '\xff1c') + { + if (next == '!' || next < ' ' + || (next >= 'a' && next <= 'z') + || (next >= 'A' && next <= 'Z')) + { + validationFailureIndex = idx - 1; + return true; + } + } + else if (current == '&' && next == '#') + { + validationFailureIndex = idx - 1; + return true; + } + + current = next; + } + + return false; + } + + public void ValidateInput() + { + validate_cookies = true; + validate_query_string = true; + validate_form = true; + } + + bool IsContentType(string ct, bool starts_with) + { + if (ct == null || ContentType == null) return false; + + if (starts_with) + return StrUtils.StartsWith(ContentType, ct, true); + + return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0; + } + + + + + + void LoadWwwForm() + { + using (Stream input = GetSubStream(InputStream)) + { + using (StreamReader s = new StreamReader(input, ContentEncoding)) + { + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + int c; + + while ((c = s.Read()) != -1) + { + if (c == '=') + { + value.Length = 0; + while ((c = s.Read()) != -1) + { + if (c == '&') + { + AddRawKeyValue(key, value); + break; + } + else + value.Append((char)c); + } + if (c == -1) + { + AddRawKeyValue(key, value); + return; + } + } + else if (c == '&') + AddRawKeyValue(key, value); + else + key.Append((char)c); + } + if (c == -1) + AddRawKeyValue(key, value); + + EndSubStream(input); + } + } + } + + void AddRawKeyValue(StringBuilder key, StringBuilder value) + { + string decodedKey = HttpUtility.UrlDecode(key.ToString(), ContentEncoding); + form.Add(decodedKey, + HttpUtility.UrlDecode(value.ToString(), ContentEncoding)); + + key.Length = 0; + value.Length = 0; + } + + WebROCollection form; + + HttpFileCollection files; + + public sealed class HttpFileCollection : NameObjectCollectionBase + { + internal HttpFileCollection() + { + } + + internal void AddFile(string name, HttpPostedFile file) + { + BaseAdd(name, file); + } + + public void CopyTo(Array dest, int index) + { + /* XXX this is kind of gross and inefficient + * since it makes a copy of the superclass's + * list */ + object[] values = BaseGetAllValues(); + values.CopyTo(dest, index); + } + + public string GetKey(int index) + { + return BaseGetKey(index); + } + + public HttpPostedFile Get(int index) + { + return (HttpPostedFile)BaseGet(index); + } + + public HttpPostedFile Get(string key) + { + return (HttpPostedFile)BaseGet(key); + } + + public HttpPostedFile this[string key] + { + get + { + return Get(key); + } + } + + public HttpPostedFile this[int index] + { + get + { + return Get(index); + } + } + + public string[] AllKeys + { + get + { + return BaseGetAllKeys(); + } + } + } + class WebROCollection : NameValueCollection + { + bool got_id; + int id; + + public bool GotID + { + get { return got_id; } + } + + public int ID + { + get { return id; } + set + { + got_id = true; + id = value; + } + } + public void Protect() + { + IsReadOnly = true; + } + + public void Unprotect() + { + IsReadOnly = false; + } + + public override string ToString() + { + StringBuilder result = new StringBuilder(); + foreach (string key in AllKeys) + { + if (result.Length > 0) + result.Append('&'); + + if (key != null && key.Length > 0) + { + result.Append(key); + result.Append('='); + } + result.Append(Get(key)); + } + + return result.ToString(); + } + } + + public sealed class HttpPostedFile + { + string name; + string content_type; + Stream stream; + + class ReadSubStream : Stream + { + Stream s; + long offset; + long end; + long position; + + public ReadSubStream(Stream s, long offset, long length) + { + this.s = s; + this.offset = offset; + this.end = offset + length; + position = offset; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int dest_offset, int count) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + if (dest_offset < 0) + throw new ArgumentOutOfRangeException("dest_offset", "< 0"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", "< 0"); + + int len = buffer.Length; + if (dest_offset > len) + throw new ArgumentException("destination offset is beyond array size"); + // reordered to avoid possible integer overflow + if (dest_offset > len - count) + throw new ArgumentException("Reading would overrun buffer"); + + if (count > end - position) + count = (int)(end - position); + + if (count <= 0) + return 0; + + s.Position = position; + int result = s.Read(buffer, dest_offset, count); + if (result > 0) + position += result; + else + position = end; + + return result; + } + + public override int ReadByte() + { + if (position >= end) + return -1; + + s.Position = position; + int result = s.ReadByte(); + if (result < 0) + position = end; + else + position++; + + return result; + } + + public override long Seek(long d, SeekOrigin origin) + { + long real; + switch (origin) + { + case SeekOrigin.Begin: + real = offset + d; + break; + case SeekOrigin.End: + real = end + d; + break; + case SeekOrigin.Current: + real = position + d; + break; + default: + throw new ArgumentException(); + } + + long virt = real - offset; + if (virt < 0 || virt > Length) + throw new ArgumentException(); + + position = s.Seek(real, SeekOrigin.Begin); + return position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead + { + get { return true; } + } + public override bool CanSeek + { + get { return true; } + } + public override bool CanWrite + { + get { return false; } + } + + public override long Length + { + get { return end - offset; } + } + + public override long Position + { + get + { + return position - offset; + } + set + { + if (value > Length) + throw new ArgumentOutOfRangeException(); + + position = Seek(value, SeekOrigin.Begin); + } + } + } + + internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) + { + this.name = name; + this.content_type = content_type; + this.stream = new ReadSubStream(base_stream, offset, length); + } + + public string ContentType + { + get + { + return (content_type); + } + } + + public int ContentLength + { + get + { + return (int)stream.Length; + } + } + + public string FileName + { + get + { + return (name); + } + } + + public Stream InputStream + { + get + { + return (stream); + } + } + + public void SaveAs(string filename) + { + byte[] buffer = new byte[16 * 1024]; + long old_post = stream.Position; + + try + { + File.Delete(filename); + using (FileStream fs = File.Create(filename)) + { + stream.Position = 0; + int n; + + while ((n = stream.Read(buffer, 0, 16 * 1024)) != 0) + { + fs.Write(buffer, 0, n); + } + } + } + finally + { + stream.Position = old_post; + } + } + } + + class Helpers + { + public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture; + } + + internal sealed class StrUtils + { + StrUtils() { } + + public static bool StartsWith(string str1, string str2) + { + return StartsWith(str1, str2, false); + } + + public static bool StartsWith(string str1, string str2, bool ignore_case) + { + int l2 = str2.Length; + if (l2 == 0) + return true; + + int l1 = str1.Length; + if (l2 > l1) + return false; + + return (0 == String.Compare(str1, 0, str2, 0, l2, ignore_case, Helpers.InvariantCulture)); + } + + public static bool EndsWith(string str1, string str2) + { + return EndsWith(str1, str2, false); + } + + public static bool EndsWith(string str1, string str2, bool ignore_case) + { + int l2 = str2.Length; + if (l2 == 0) + return true; + + int l1 = str1.Length; + if (l2 > l1) + return false; + + return (0 == String.Compare(str1, l1 - l2, str2, 0, l2, ignore_case, Helpers.InvariantCulture)); + } + } + + class HttpMultipart + { + + public class Element + { + public string ContentType; + public string Name; + public string Filename; + public Encoding Encoding; + public long Start; + public long Length; + + public override string ToString() + { + return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + + Start.ToString() + ", Length " + Length.ToString(); + } + } + + Stream data; + string boundary; + byte[] boundary_bytes; + byte[] buffer; + bool at_eof; + Encoding encoding; + StringBuilder sb; + + const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r'; + + // See RFC 2046 + // In the case of multipart entities, in which one or more different + // sets of data are combined in a single body, a "multipart" media type + // field must appear in the entity's header. The body must then contain + // one or more body parts, each preceded by a boundary delimiter line, + // and the last one followed by a closing boundary delimiter line. + // After its boundary delimiter line, each body part then consists of a + // header area, a blank line, and a body area. Thus a body part is + // similar to an RFC 822 message in syntax, but different in meaning. + + public HttpMultipart(Stream data, string b, Encoding encoding) + { + this.data = data; + //DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET + //var ms = new MemoryStream(32 * 1024); + //data.CopyTo(ms); + //this.data = ms; + + boundary = b; + boundary_bytes = encoding.GetBytes(b); + buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' + this.encoding = encoding; + sb = new StringBuilder(); + } + + string ReadLine() + { + // CRLF or LF are ok as line endings. + bool got_cr = false; + int b = 0; + sb.Length = 0; + while (true) + { + b = data.ReadByte(); + if (b == -1) + { + return null; + } + + if (b == LF) + { + break; + } + got_cr = (b == CR); + sb.Append((char)b); + } + + if (got_cr) + sb.Length--; + + return sb.ToString(); + + } + + static string GetContentDispositionAttribute(string l, string name) + { + int idx = l.IndexOf(name + "=\""); + if (idx < 0) + return null; + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + return null; + if (begin == end) + return ""; + return l.Substring(begin, end - begin); + } + + string GetContentDispositionAttributeWithEncoding(string l, string name) + { + int idx = l.IndexOf(name + "=\""); + if (idx < 0) + return null; + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + return null; + if (begin == end) + return ""; + + string temp = l.Substring(begin, end - begin); + byte[] source = new byte[temp.Length]; + for (int i = temp.Length - 1; i >= 0; i--) + source[i] = (byte)temp[i]; + + return encoding.GetString(source); + } + + bool ReadBoundary() + { + try + { + string line = ReadLine(); + while (line == "") + line = ReadLine(); + if (line[0] != '-' || line[1] != '-') + return false; + + if (!StrUtils.EndsWith(line, boundary, false)) + return true; + } + catch + { + } + + return false; + } + + string ReadHeaders() + { + string s = ReadLine(); + if (s == "") + return null; + + return s; + } + + bool CompareBytes(byte[] orig, byte[] other) + { + for (int i = orig.Length - 1; i >= 0; i--) + if (orig[i] != other[i]) + return false; + + return true; + } + + long MoveToNextBoundary() + { + long retval = 0; + bool got_cr = false; + + int state = 0; + int c = data.ReadByte(); + while (true) + { + if (c == -1) + return -1; + + if (state == 0 && c == LF) + { + retval = data.Position - 1; + if (got_cr) + retval--; + state = 1; + c = data.ReadByte(); + } + else if (state == 0) + { + got_cr = (c == CR); + c = data.ReadByte(); + } + else if (state == 1 && c == '-') + { + c = data.ReadByte(); + if (c == -1) + return -1; + + if (c != '-') + { + state = 0; + got_cr = false; + continue; // no ReadByte() here + } + + int nread = data.Read(buffer, 0, buffer.Length); + int bl = buffer.Length; + if (nread != bl) + return -1; + + if (!CompareBytes(boundary_bytes, buffer)) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + c = data.ReadByte(); + continue; + } + + if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') + { + at_eof = true; + } + else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + c = data.ReadByte(); + continue; + } + data.Position = retval + 2; + if (got_cr) + data.Position++; + break; + } + else + { + // state == 1 + state = 0; // no ReadByte() here + } + } + + return retval; + } + + public Element ReadNextElement() + { + if (at_eof || ReadBoundary()) + return null; + + Element elem = new Element(); + string header; + while ((header = ReadHeaders()) != null) + { + if (StrUtils.StartsWith(header, "Content-Disposition:", true)) + { + elem.Name = GetContentDispositionAttribute(header, "name"); + elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); + } + else if (StrUtils.StartsWith(header, "Content-Type:", true)) + { + elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + elem.Encoding = GetEncoding(elem.ContentType); + } + } + + long start = 0; + start = data.Position; + elem.Start = start; + long pos = MoveToNextBoundary(); + if (pos == -1) + return null; + + elem.Length = pos - start; + return elem; + } + + static string StripPath(string path) + { + if (path == null || path.Length == 0) + return path; + + if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\")) + return path; + return path.Substring(path.LastIndexOf('\\') + 1); + } + } + + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs new file mode 100644 index 000000000..412789240 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -0,0 +1,157 @@ +using System.Text; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType; +using WebSocketState = MediaBrowser.Model.Net.WebSocketState; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public class SharpWebSocket : IWebSocket + { + /// + /// The logger + /// + private readonly ILogger _logger; + + public event EventHandler Closed; + + /// + /// Gets or sets the web socket. + /// + /// The web socket. + private WebSocketSharp.WebSocket WebSocket { get; set; } + + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + /// + /// Initializes a new instance of the class. + /// + /// The socket. + /// The logger. + /// socket + public SharpWebSocket(WebSocketSharp.WebSocket socket, ILogger logger) + { + if (socket == null) + { + throw new ArgumentNullException("socket"); + } + + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + _logger = logger; + WebSocket = socket; + + socket.OnMessage += socket_OnMessage; + socket.OnClose += socket_OnClose; + socket.OnError += socket_OnError; + + WebSocket.ConnectAsServer(); + } + + void socket_OnError(object sender, WebSocketSharp.ErrorEventArgs e) + { + EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); + } + + void socket_OnClose(object sender, WebSocketSharp.CloseEventArgs e) + { + EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); + } + + void socket_OnMessage(object sender, WebSocketSharp.MessageEventArgs e) + { + if (OnReceive != null) + { + OnReceiveBytes(e.RawData); + } + } + + /// + /// Gets or sets the state. + /// + /// The state. + public WebSocketState State + { + get + { + WebSocketState commonState; + + if (!Enum.TryParse(WebSocket.ReadyState.ToString(), true, out commonState)) + { + _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.ReadyState.ToString()); + } + + return commonState; + } + } + + /// + /// 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) + { + System.Net.WebSockets.WebSocketMessageType nativeType; + + if (!Enum.TryParse(type.ToString(), true, out nativeType)) + { + _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString()); + } + + var completionSource = new TaskCompletionSource(); + + WebSocket.SendAsync(Encoding.UTF8.GetString(bytes), res => completionSource.TrySetResult(true)); + + return completionSource.Task; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + WebSocket.OnMessage -= socket_OnMessage; + WebSocket.OnClose -= socket_OnClose; + WebSocket.OnError -= socket_OnError; + + _cancellationTokenSource.Cancel(); + + WebSocket.Close(); + } + } + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceiveBytes { get; set; } + + /// + /// Gets or sets the on receive. + /// + /// The on receive. + public Action OnReceive { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs new file mode 100644 index 000000000..cf756d9f2 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -0,0 +1,192 @@ +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; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public class WebSocketSharpListener : IHttpListener + { + private readonly ConcurrentDictionary _localEndPoints = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private WebSocketSharp.Server.HttpServer _httpsv; + + private readonly ILogger _logger; + private readonly SmartThreadPool _threadPoolManager; + + public WebSocketSharpListener(ILogger logger, SmartThreadPool threadPoolManager) + { + _logger = logger; + _threadPoolManager = threadPoolManager; + } + + public IEnumerable LocalEndPoints + { + get { return _localEndPoints.Keys.ToList(); } + } + + public System.Action ErrorHandler { get; set; } + + public System.Func RequestHandler { get; set; } + + public Action WebSocketHandler { get; set; } + + public void Start(IEnumerable urlPrefixes) + { + _httpsv = new WebSocketSharp.Server.HttpServer(8096, false, urlPrefixes.First()); + + _httpsv.OnRequest += _httpsv_OnRequest; + + _httpsv.Start(); + } + + void _httpsv_OnRequest(object sender, HttpRequestEventArgs e) + { + _threadPoolManager.QueueWorkItem(() => InitTask(e.Context)); + } + + private void InitTask(HttpListenerContext context) + { + try + { + var task = this.ProcessRequestAsync(context); + task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); + + if (task.Status == TaskStatus.Created) + { + task.RunSynchronously(); + } + } + catch (Exception ex) + { + HandleError(ex, context); + } + } + + private Task ProcessRequestAsync(HttpListenerContext context) + { + var request = context.Request; + + LogHttpRequest(request); + + if (request.IsWebSocketRequest) + { + ProcessWebSocketRequest(context); + return Task.FromResult(true); + } + + if (string.IsNullOrEmpty(context.Request.RawUrl)) + return ((object)null).AsTaskResult(); + + var httpReq = GetRequest(context); + + return RequestHandler(httpReq, request.Url); + } + + /// + /// Logs the HTTP request. + /// + /// The request. + private void LogHttpRequest(HttpListenerRequest request) + { + var endpoint = request.LocalEndPoint; + + if (endpoint != null) + { + var address = endpoint.ToString(); + + _localEndPoints.GetOrAdd(address, address); + } + + LogRequest(_logger, request); + } + + private void ProcessWebSocketRequest(HttpListenerContext ctx) + { + try + { + var webSocketContext = ctx.AcceptWebSocket(null, null); + + if (WebSocketHandler != null) + { + WebSocketHandler(new WebSocketConnectEventArgs + { + WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), + Endpoint = ctx.Request.RemoteEndPoint.ToString() + }); + } + } + catch (Exception ex) + { + _logger.ErrorException("AcceptWebSocketAsync error", ex); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } + } + + private IHttpRequest GetRequest(HttpListenerContext httpContext) + { + var operationName = httpContext.Request.GetOperationName(); + + var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger); + req.RequestAttributes = req.GetAttributes(); + + return req; + } + + /// + /// Logs the request. + /// + /// The logger. + /// The request. + private static void LogRequest(ILogger logger, HttpListenerRequest request) + { + var log = new StringBuilder(); + + //var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); + + //log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); + + var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; + + logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log); + } + + private void HandleError(Exception ex, HttpListenerContext context) + { + var httpReq = GetRequest(context); + + if (ErrorHandler != null) + { + ErrorHandler(ex, httpReq); + } + } + + public void Stop() + { + _httpsv.Stop(); + } + + private readonly object _disposeLock = new object(); + public void Dispose() + { + lock (_disposeLock) + { + if (_httpsv != null) + { + _httpsv.OnRequest -= _httpsv_OnRequest; + _httpsv.Stop(); + _httpsv = null; + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs new file mode 100644 index 000000000..7a5f6fbdc --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Funq; +using MediaBrowser.Model.Logging; +using ServiceStack; +using ServiceStack.Host; +using ServiceStack.Web; +using WebSocketSharp.Net; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + public Container Container { get; set; } + private readonly HttpListenerRequest request; + private readonly IHttpResponse response; + + public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger) + { + this.OperationName = operationName; + this.RequestAttributes = requestAttributes; + this.request = httpContext.Request; + this.response = new WebSocketSharpResponse(logger, httpContext.Response); + + this.RequestPreferences = new RequestPreferences(this); + } + + public HttpListenerRequest HttpRequest + { + get { return request; } + } + + public object OriginalRequest + { + get { return request; } + } + + public IResponse Response + { + get { return response; } + } + + public IHttpResponse HttpResponse + { + get { return response; } + } + + public RequestAttributes RequestAttributes { get; set; } + + public IRequestPreferences RequestPreferences { get; private set; } + + public T TryResolve() + { + if (typeof(T) == typeof(IHttpRequest)) + throw new Exception("You don't need to use IHttpRequest.TryResolve to resolve itself"); + + if (typeof(T) == typeof(IHttpResponse)) + throw new Exception("Resolve IHttpResponse with 'Response' property instead of IHttpRequest.TryResolve"); + + return Container == null + ? HostContext.TryResolve() + : Container.TryResolve(); + } + + public string OperationName { get; set; } + + public object Dto { get; set; } + + public string GetRawBody() + { + if (bufferedStream != null) + { + return bufferedStream.ToArray().FromUtf8Bytes(); + } + + using (var reader = new StreamReader(InputStream)) + { + return reader.ReadToEnd(); + } + } + + public string RawUrl + { + get { return request.RawUrl; } + } + + public string AbsoluteUri + { + get { return request.Url.AbsoluteUri.TrimEnd('/'); } + } + + public string UserHostAddress + { + get { return request.UserHostAddress; } + } + + public string XForwardedFor + { + get + { + return String.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedFor]) ? null : request.Headers[HttpHeaders.XForwardedFor]; + } + } + + public int? XForwardedPort + { + get + { + return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedPort]) ? (int?)null : int.Parse(request.Headers[HttpHeaders.XForwardedPort]); + } + } + + public string XForwardedProtocol + { + get + { + return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedProtocol]) ? null : request.Headers[HttpHeaders.XForwardedProtocol]; + } + } + + public string XRealIp + { + get + { + return String.IsNullOrEmpty(request.Headers[HttpHeaders.XRealIp]) ? null : request.Headers[HttpHeaders.XRealIp]; + } + } + + private string remoteIp; + public string RemoteIp + { + get + { + return remoteIp ?? + (remoteIp = XForwardedFor ?? + (XRealIp ?? + ((request.RemoteEndPoint != null) ? request.RemoteEndPoint.Address.ToString() : null))); + } + } + + public bool IsSecureConnection + { + get { return request.IsSecureConnection || XForwardedProtocol == "https"; } + } + + public string[] AcceptTypes + { + get { return request.AcceptTypes; } + } + + private Dictionary items; + public Dictionary Items + { + get { return items ?? (items = new Dictionary()); } + } + + private string responseContentType; + public string ResponseContentType + { + get + { + return responseContentType + ?? (responseContentType = this.GetResponseContentType()); + } + set + { + this.responseContentType = value; + HasExplicitResponseContentType = true; + } + } + + public bool HasExplicitResponseContentType { get; private set; } + + private string pathInfo; + public string PathInfo + { + get + { + if (this.pathInfo == null) + { + var mode = HostContext.Config.HandlerFactoryPath; + + var pos = request.RawUrl.IndexOf("?"); + if (pos != -1) + { + var path = request.RawUrl.Substring(0, pos); + this.pathInfo = HttpRequestExtensions.GetPathInfo( + path, + mode, + mode ?? ""); + } + else + { + this.pathInfo = request.RawUrl; + } + + this.pathInfo = this.pathInfo.UrlDecode(); + this.pathInfo = NormalizePathInfo(pathInfo, mode); + } + return this.pathInfo; + } + } + + private Dictionary cookies; + public IDictionary Cookies + { + get + { + if (cookies == null) + { + cookies = new Dictionary(); + for (var i = 0; i < this.request.Cookies.Count; i++) + { + var httpCookie = this.request.Cookies[i]; + cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); + } + } + + return cookies; + } + } + + public string UserAgent + { + get { return request.UserAgent; } + } + + private NameValueCollectionWrapper headers; + public INameValueCollection Headers + { + get { return headers ?? (headers = new NameValueCollectionWrapper(request.Headers)); } + } + + private NameValueCollectionWrapper queryString; + public INameValueCollection QueryString + { + get { return queryString ?? (queryString = new NameValueCollectionWrapper(HttpUtility.ParseQueryString(request.Url.Query))); } + } + + private NameValueCollectionWrapper formData; + public INameValueCollection FormData + { + get { return formData ?? (formData = new NameValueCollectionWrapper(this.Form)); } + } + + public bool IsLocal + { + get { return request.IsLocal; } + } + + private string httpMethod; + public string HttpMethod + { + get + { + return httpMethod + ?? (httpMethod = Param(HttpHeaders.XHttpMethodOverride) + ?? request.HttpMethod); + } + } + + public string Verb + { + get { return HttpMethod; } + } + + public string Param(string name) + { + return Headers[name] + ?? QueryString[name] + ?? FormData[name]; + } + + public string ContentType + { + get { return request.ContentType; } + } + + public Encoding contentEncoding; + public Encoding ContentEncoding + { + get { return contentEncoding ?? request.ContentEncoding; } + set { contentEncoding = value; } + } + + public Uri UrlReferrer + { + get { return request.UrlReferrer; } + } + + public static Encoding GetEncoding(string contentTypeHeader) + { + var param = GetParameter(contentTypeHeader, "charset="); + if (param == null) return null; + try + { + return Encoding.GetEncoding(param); + } + catch (ArgumentException) + { + return null; + } + } + + public bool UseBufferedStream + { + get { return bufferedStream != null; } + set + { + bufferedStream = value + ? bufferedStream ?? new MemoryStream(request.InputStream.ReadFully()) + : null; + } + } + + private MemoryStream bufferedStream; + public Stream InputStream + { + get { return bufferedStream ?? request.InputStream; } + } + + public long ContentLength + { + get { return request.ContentLength64; } + } + + private IHttpFile[] httpFiles; + public IHttpFile[] Files + { + get + { + if (httpFiles == null) + { + if (files == null) + return httpFiles = new IHttpFile[0]; + + httpFiles = new IHttpFile[files.Count]; + for (var i = 0; i < files.Count; i++) + { + var reqFile = files[i]; + + httpFiles[i] = new HttpFile + { + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; + } + } + return httpFiles; + } + } + + static Stream GetSubStream(Stream stream) + { + if (stream is MemoryStream) + { + var other = (MemoryStream)stream; + try + { + return new MemoryStream(other.GetBuffer(), 0, (int)other.Length, false, true); + } + catch (UnauthorizedAccessException) + { + return new MemoryStream(other.ToArray(), 0, (int)other.Length, false, true); + } + } + + return stream; + } + + static void EndSubStream(Stream stream) + { + } + + public static string GetHandlerPathIfAny(string listenerUrl) + { + if (listenerUrl == null) return null; + var pos = listenerUrl.IndexOf("://", StringComparison.InvariantCultureIgnoreCase); + if (pos == -1) return null; + var startHostUrl = listenerUrl.Substring(pos + "://".Length); + var endPos = startHostUrl.IndexOf('/'); + if (endPos == -1) return null; + var endHostUrl = startHostUrl.Substring(endPos + 1); + return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); + } + + public static string NormalizePathInfo(string pathInfo, string handlerPath) + { + if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( + handlerPath, StringComparison.InvariantCultureIgnoreCase)) + { + return pathInfo.TrimStart('/').Substring(handlerPath.Length); + } + + return pathInfo; + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs new file mode 100644 index 000000000..2e3828512 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -0,0 +1,144 @@ +using System; +using System.IO; +using System.Net; +using MediaBrowser.Model.Logging; +using ServiceStack; +using ServiceStack.Host; +using ServiceStack.Web; +using HttpListenerResponse = WebSocketSharp.Net.HttpListenerResponse; + +namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp +{ + public class WebSocketSharpResponse : IHttpResponse + { + private readonly ILogger _logger; + private readonly HttpListenerResponse response; + + public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response) + { + _logger = logger; + this.response = response; + } + + public bool UseBufferedStream { get; set; } + + public object OriginalResponse + { + get { return response; } + } + + public int StatusCode + { + get { return this.response.StatusCode; } + set { this.response.StatusCode = value; } + } + + public string StatusDescription + { + get { return this.response.StatusDescription; } + set { this.response.StatusDescription = value; } + } + + public string ContentType + { + get { return response.ContentType; } + set { response.ContentType = value; } + } + + public ICookies Cookies { get; set; } + + public void AddHeader(string name, string value) + { + if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) + { + ContentType = value; + return; + } + + response.AddHeader(name, value); + } + + public void Redirect(string url) + { + response.Redirect(url); + } + + public Stream OutputStream + { + get { return response.OutputStream; } + } + + public object Dto { get; set; } + + public void Write(string text) + { + try + { + var bOutput = System.Text.Encoding.UTF8.GetBytes(text); + response.ContentLength64 = bOutput.Length; + + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + Close(); + } + catch (Exception ex) + { + _logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex); + throw; + } + } + + public void Close() + { + if (!this.IsClosed) + { + this.IsClosed = true; + + try + { + this.response.CloseOutputStream(_logger); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing HttpListener output stream", ex); + } + } + } + + public void End() + { + Close(); + } + + public void Flush() + { + response.OutputStream.Flush(); + } + + public bool IsClosed + { + get; + private set; + } + + public void SetContentLength(long contentLength) + { + //you can happily set the Content-Length header in Asp.Net + //but HttpListener will complain if you do - you have to set ContentLength64 on the response. + //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header + response.ContentLength64 = contentLength; + } + + public void SetCookie(Cookie cookie) + { + var cookieStr = cookie.AsHeaderValue(); + response.Headers.Add(HttpHeaders.SetCookie, cookieStr); + } + + public bool SendChunked + { + get { return response.SendChunked; } + set { response.SendChunked = value; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index c24e9574f..38600922b 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -45,9 +45,6 @@ v4.5 - - ..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll - False ..\packages\Mono.Nat.1.2.20.0\lib\net40\Mono.Nat.dll @@ -154,9 +151,15 @@ + + + + + + @@ -270,8 +273,6 @@ - - diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index 5931d7718..6f14bb322 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -45,12 +45,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager get { return _webSocketConnections; } } - /// - /// Gets or sets the external web socket server. - /// - /// The external web socket server. - private IWebSocketServer ExternalWebSocketServer { get; set; } - /// /// The _logger /// @@ -67,22 +61,13 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// The configuration manager. private IServerConfigurationManager ConfigurationManager { get; set; } - /// - /// Gets a value indicating whether [supports web socket]. - /// - /// true if [supports web socket]; otherwise, false. - public bool SupportsNativeWebSocket - { - get { return HttpServer != null && HttpServer.SupportsWebSockets; } - } - /// /// Gets the web socket port number. /// /// The web socket port number. public int WebSocketPortNumber { - get { return SupportsNativeWebSocket ? ConfigurationManager.Configuration.HttpServerPortNumber : ConfigurationManager.Configuration.LegacyWebSocketPortNumber; } + get { return ConfigurationManager.Configuration.HttpServerPortNumber; } } /// @@ -128,27 +113,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager ReloadHttpServer(urlPrefixes); } - public void StartWebSocketServer() - { - if (!SupportsNativeWebSocket) - { - ReloadExternalWebSocketServer(ConfigurationManager.Configuration.LegacyWebSocketPortNumber); - } - } - - /// - /// Starts the external web socket server. - /// - private void ReloadExternalWebSocketServer(int portNumber) - { - DisposeExternalWebSocketServer(); - - ExternalWebSocketServer = _applicationHost.Resolve(); - - ExternalWebSocketServer.Start(portNumber); - ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected; - } - /// /// Restarts the Http Server, or starts it if not currently running /// @@ -325,8 +289,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager HttpServer.WebSocketConnected -= HttpServer_WebSocketConnected; HttpServer.Dispose(); } - - DisposeExternalWebSocketServer(); } /// @@ -350,18 +312,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager } } - /// - /// Disposes the external web socket server. - /// - private void DisposeExternalWebSocketServer() - { - if (ExternalWebSocketServer != null) - { - _logger.Info("Disposing {0}", ExternalWebSocketServer.GetType().Name); - ExternalWebSocketServer.Dispose(); - } - } - /// /// Adds the web socket listeners. /// diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs deleted file mode 100644 index 454dff4b9..000000000 --- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Alchemy; -using Alchemy.Classes; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; -using System; -using System.Net; -#if __MonoCS__ -using Mono.Unix.Native; -#endif - -namespace MediaBrowser.Server.Implementations.WebSocket -{ - /// - /// Class AlchemyServer - /// - public class AlchemyServer : IWebSocketServer - { - /// - /// Occurs when [web socket connected]. - /// - public event EventHandler WebSocketConnected; - - /// - /// Gets or sets the web socket server. - /// - /// The web socket server. - private WebSocketServer WebSocketServer { get; set; } - - /// - /// The _logger - /// - private readonly ILogger _logger; - - private bool _hasStopped; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// logger - public AlchemyServer(ILogger logger) - { - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - _logger = logger; - } - - /// - /// Gets the port. - /// - /// The port. - public int Port { get; private set; } - - /// - /// Starts the specified port number. - /// - /// The port number. - public void Start(int portNumber) - { - _logger.Info("Starting Alchemy web socket server on port {0}", portNumber); - - try - { - WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any) - { - OnConnected = OnAlchemyWebSocketClientConnected, - TimeOut = TimeSpan.FromHours(24) - }; - - #if __MonoCS__ - //Linux: port below 1024 require root or cap_net_bind_service - if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) - { - if (Syscall.getuid() == 0) - { - WebSocketServer.FlashAccessPolicyEnabled = true; - } - else - { - WebSocketServer.FlashAccessPolicyEnabled = false; - } - } - #endif - WebSocketServer.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("The web socket server is unable to start on port {0} due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system.", ex, portNumber); - - throw; - } - - Port = portNumber; - - _logger.Info("Alchemy Web Socket Server started"); - } - - /// - /// Called when [alchemy web socket client connected]. - /// - /// The context. - private void OnAlchemyWebSocketClientConnected(UserContext context) - { - if (_hasStopped) - { - return; - } - - if (WebSocketConnected != null) - { - var socket = new AlchemyWebSocket(context, _logger); - - WebSocketConnected(this, new WebSocketConnectEventArgs - { - WebSocket = socket, - Endpoint = context.ClientAddress.ToString() - }); - } - } - - /// - /// Stops this instance. - /// - public void Stop() - { - if (WebSocketServer != null) - { - WebSocketServer.Stop(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private readonly object _syncLock = new object(); - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - _hasStopped = true; - - lock (_syncLock) - { - if (WebSocketServer != null) - { - _logger.Debug("Disposing alchemy server"); - - WebSocketServer.Dispose(); - WebSocketServer = null; - } - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs deleted file mode 100644 index 35c5e780b..000000000 --- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Alchemy.Classes; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.WebSocket -{ - /// - /// Class AlchemyWebSocket - /// - public class AlchemyWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private UserContext UserContext { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The context. - /// The logger. - /// context - public AlchemyWebSocket(UserContext context, ILogger logger) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - _logger = logger; - UserContext = context; - - context.SetOnDisconnect(OnDisconnected); - context.SetOnReceive(OnReceiveContext); - - _logger.Info("Client connected from {0}", context.ClientAddress); - } - - /// - /// The _disconnected - /// - private bool _disconnected; - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State - { - get { return _disconnected ? WebSocketState.Closed : WebSocketState.Open; } - } - - /// - /// Called when [disconnected]. - /// - /// The context. - private void OnDisconnected(UserContext context) - { - _disconnected = true; - - EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); - } - - /// - /// Called when [receive]. - /// - /// The context. - private void OnReceiveContext(UserContext context) - { - if (OnReceive != null) - { - var json = context.DataFrame.ToString(); - - OnReceive(json); - } - } - - private readonly Task _cachedTask = Task.FromResult(true); - /// - /// 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) - { - UserContext.Send(bytes); - - return _cachedTask; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - - /// - /// Gets or sets the on receive. - /// - /// The on receive. - public Action OnReceive { get; set; } - } -} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index c22d3bec3..907500ae3 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,6 +1,5 @@  - diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index da7f8631b..3d6865f85 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -72,7 +72,6 @@ using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; using MediaBrowser.Server.Implementations.Sync; using MediaBrowser.Server.Implementations.Themes; -using MediaBrowser.Server.Implementations.WebSocket; using MediaBrowser.ServerApplication.EntryPoints; using MediaBrowser.ServerApplication.FFMpeg; using MediaBrowser.ServerApplication.IO; @@ -517,8 +516,6 @@ namespace MediaBrowser.ServerApplication LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer); RegisterSingleInstance(LocalizationManager); - RegisterSingleInstance(() => new AlchemyServer(Logger)); - RegisterSingleInstance(() => new BdInfoExaminer()); UserDataManager = new UserDataManager(LogManager); @@ -868,8 +865,6 @@ namespace MediaBrowser.ServerApplication throw; } } - - ServerManager.StartWebSocketServer(); } /// @@ -885,11 +880,6 @@ namespace MediaBrowser.ServerApplication { NotifyPendingRestart(); } - - else if (!ServerManager.SupportsNativeWebSocket && ServerManager.WebSocketPortNumber != ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber) - { - NotifyPendingRestart(); - } } /// @@ -1022,7 +1012,7 @@ namespace MediaBrowser.ServerApplication Version = ApplicationVersion.ToString(), IsNetworkDeployed = CanSelfUpdate, WebSocketPortNumber = ServerManager.WebSocketPortNumber, - SupportsNativeWebSocket = ServerManager.SupportsNativeWebSocket, + SupportsNativeWebSocket = true, FailedPluginAssemblies = FailedAssemblies.ToList(), InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToList(), CompletedInstallations = InstallationManager.CompletedInstallations.ToList(), -- 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.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 993b69601..768e9a4a8 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 a098d26da..f01d8ddf4 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 178b840a8..850309465 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 78f60632f..38b6b4ad6 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 05f7975bc..b31a95319 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 9f9dfaeca..b7715e20b 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 88eadda00..af0ff8319 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 904c7bcdd..3448a2905 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 62a43751d..f530564ea 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 f89cdac47..c7669fecb 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 412789240..7ff3a1247 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 6b376d3b4..a756dc794 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 eb1334c10..1192bafb7 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 f31718893..37a5735e4 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 a7f714175..92f352ca6 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 2b70bf74e..988b03650 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 be4cf1bbf..58b273c26 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 a033dcbc9..9ad65edbd 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 59dbb63e6..03694cad0 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 a0fc056bc..bfd93b795 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 5c199c8ea..05647f10d 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 1ca3aa56d..62ecc2578 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 0919afc7d..e0b199b12 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 6310881f2..2cb25ef9b 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 96e71a971..1963f249a 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 187bcb59f..0b6e7c9c5 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 a15a12878..113389a11 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 30a6b8e26..3ed0c8735 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 6f726759d..c1700fe51 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 3f0d01084..10275847d 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 b97935dde..3375c1a1d 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 367e971d8..aac6a2c05 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 c7f7c0f69..eb3740b84 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 a7d6690f0..ea32fb932 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 a0ee4fdaf..421a0dea9 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 b9b21645b..0f93d0695 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 5408f0b91..fe7d37994 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 59438e2b5..df4178c6a 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 1de1534e7..461e56f49 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 507045cbb..d6cdfe028 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 55c296948..8db9c68f8 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 cfb0924d2..90c4c9e52 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 b6c94c512..b778dc415 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 48a5cc004..653811157 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 16700e356..f315798ec 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 45b3b5c78..18e44a03c 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 733e37706..55439632f 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 ccfccea6a..fc02a9e8c 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 473b2523f..effeb0ad6 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 77d265bd4..2757333f1 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 caee46351..b588e6c11 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 18cc943e1..e1a405972 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 f90a35018..515cd70a2 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 94927eb12..c2afde015 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 7f1545c2b..4e01acfa1 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 43444b689..b5b610654 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 e60c7b3c3..b635d7318 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 c53ecd9d8..12850f803 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 7d9e83cbc..fcf3756ab 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 741bc52ca..4a23b0c07 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 75e204f68..e28dfe231 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 cc5a77433..78f308b33 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 c2c8f5002..e67aa9f58 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 6f14bb322..a65184f61 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 2b60f3116..3c3d7740a 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 3d6865f85..7c36a7c5e 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 9e31fc800..f8b03bbc2 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 d762dfaf7..350412344 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 d2e542536..e5989db3b 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 cbd0ce4a1..545894b0d 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 725a5edcb..104e63447 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 b50cb254f..64c055ba0 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 176094b75..06a44d747 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 c524f3919ef019092d3bcfa246870bc3f16070c5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 21 Jul 2014 21:29:06 -0400 Subject: added cloud sync model objects --- MediaBrowser.Api/ApiEntryPoint.cs | 55 +----- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 5 - MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- MediaBrowser.Api/UserService.cs | 2 +- .../MediaBrowser.Common.Implementations.csproj | 6 +- .../packages.config | 2 +- .../MediaBrowser.Controller.csproj | 2 + MediaBrowser.Controller/Sync/ICloudSyncProvider.cs | 12 ++ MediaBrowser.Controller/Sync/ISyncManager.cs | 11 ++ MediaBrowser.Controller/Sync/ISyncProvider.cs | 28 +++ MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs | 154 +++++++++++++-- MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 15 +- MediaBrowser.Dlna/PlayTo/Device.cs | 19 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 59 +++--- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 39 ++-- MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs | 71 ++++--- MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs | 2 + .../Images/LocalImageProvider.cs | 2 +- .../Savers/XmlSaverHelpers.cs | 7 + .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + MediaBrowser.Model/Entities/MediaStream.cs | 6 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + MediaBrowser.Model/Sync/SyncJob.cs | 2 +- MediaBrowser.Model/Sync/SyncJobRequest.cs | 4 +- MediaBrowser.Model/Sync/SyncScheduleRequest.cs | 4 +- MediaBrowser.Model/Sync/SyncTarget.cs | 17 ++ .../HttpServer/HttpListenerHost.cs | 3 +- .../HttpServer/Security/AuthService.cs | 2 +- .../Library/UserManager.cs | 3 +- .../Localization/JavaScript/ar.json | 4 +- .../Localization/JavaScript/ca.json | 4 +- .../Localization/JavaScript/cs.json | 8 +- .../Localization/JavaScript/da.json | 4 +- .../Localization/JavaScript/de.json | 4 +- .../Localization/JavaScript/el.json | 4 +- .../Localization/JavaScript/en_GB.json | 4 +- .../Localization/JavaScript/en_US.json | 4 +- .../Localization/JavaScript/es.json | 4 +- .../Localization/JavaScript/es_MX.json | 4 +- .../Localization/JavaScript/fr.json | 218 +++++++++++---------- .../Localization/JavaScript/he.json | 4 +- .../Localization/JavaScript/it.json | 128 ++++++------ .../Localization/JavaScript/javascript.json | 4 +- .../Localization/JavaScript/kk.json | 10 +- .../Localization/JavaScript/ms.json | 4 +- .../Localization/JavaScript/nb.json | 4 +- .../Localization/JavaScript/nl.json | 8 +- .../Localization/JavaScript/pl.json | 4 +- .../Localization/JavaScript/pt_BR.json | 8 +- .../Localization/JavaScript/pt_PT.json | 4 +- .../Localization/JavaScript/ru.json | 16 +- .../Localization/JavaScript/sv.json | 4 +- .../Localization/JavaScript/vi.json | 4 +- .../Localization/JavaScript/zh_TW.json | 4 +- .../Localization/Server/ar.json | 6 +- .../Localization/Server/ca.json | 6 +- .../Localization/Server/cs.json | 18 +- .../Localization/Server/da.json | 6 +- .../Localization/Server/de.json | 6 +- .../Localization/Server/el.json | 6 +- .../Localization/Server/en_GB.json | 6 +- .../Localization/Server/en_US.json | 6 +- .../Localization/Server/es.json | 6 +- .../Localization/Server/es_MX.json | 6 +- .../Localization/Server/fr.json | 168 ++++++++-------- .../Localization/Server/he.json | 6 +- .../Localization/Server/it.json | 76 +++---- .../Localization/Server/kk.json | 50 ++--- .../Localization/Server/ko.json | 6 +- .../Localization/Server/ms.json | 6 +- .../Localization/Server/nb.json | 6 +- .../Localization/Server/nl.json | 10 +- .../Localization/Server/pl.json | 6 +- .../Localization/Server/pt_BR.json | 14 +- .../Localization/Server/pt_PT.json | 6 +- .../Localization/Server/ru.json | 110 ++++++----- .../Localization/Server/server.json | 5 +- .../Localization/Server/sv.json | 6 +- .../Localization/Server/vi.json | 6 +- .../Localization/Server/zh_TW.json | 6 +- .../MediaBrowser.Server.Implementations.csproj | 3 + .../Security/AuthenticationException.cs | 16 ++ .../Session/SessionManager.cs | 27 +-- .../Sync/AppSyncProvider.cs | 26 +++ .../Sync/CloudSyncProvider.cs | 35 ++++ .../Sync/SyncManager.cs | 13 ++ MediaBrowser.ServerApplication/ApplicationHost.cs | 14 +- .../FFMpeg/FFMpegDownloadInfo.cs | 6 +- .../FFMpeg/FFMpegDownloader.cs | 9 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 9 +- .../MediaBrowser.WebDashboard.csproj | 15 +- Nuget/MediaBrowser.Common.Internal.nuspec | 2 +- 93 files changed, 1078 insertions(+), 665 deletions(-) create mode 100644 MediaBrowser.Controller/Sync/ICloudSyncProvider.cs create mode 100644 MediaBrowser.Controller/Sync/ISyncProvider.cs create mode 100644 MediaBrowser.Model/Sync/SyncTarget.cs create mode 100644 MediaBrowser.Server.Implementations/Security/AuthenticationException.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index f255339bc..c51d9e7c0 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -302,75 +302,38 @@ namespace MediaBrowser.Api /// Kills the single transcoding job. /// /// The device id. - /// The delete. + /// The delete files. /// if set to true [acquire lock]. /// Task. + /// deviceId /// sourcePath - internal async Task KillTranscodingJobs(string deviceId, Func delete, bool acquireLock) + internal Task KillTranscodingJobs(string deviceId, Func deleteFiles, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException("deviceId"); } - var jobs = new List(); - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase))); - } - - if (jobs.Count == 0) - { - return; - } - - if (acquireLock) - { - await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - } - - try - { - foreach (var job in jobs) - { - KillTranscodingJob(job, delete); - } - } - finally - { - if (acquireLock) - { - TranscodingStartLock.Release(); - } - } + return KillTranscodingJobs(j => string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase), deleteFiles, acquireLock); } /// /// Kills the transcoding jobs. /// - /// The device identifier. - /// The type. - /// The delete. + /// The kill job. + /// The delete files. /// if set to true [acquire lock]. /// Task. /// deviceId - internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, Func delete, bool acquireLock) + internal async Task KillTranscodingJobs(Func killJob, Func deleteFiles, bool acquireLock) { - if (string.IsNullOrEmpty(deviceId)) - { - throw new ArgumentNullException("deviceId"); - } - var jobs = new List(); lock (_activeTranscodingJobs) { // This is really only needed for HLS. // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase) && i.Type == type)); + jobs.AddRange(_activeTranscodingJobs.Where(killJob)); } if (jobs.Count == 0) @@ -387,7 +350,7 @@ namespace MediaBrowser.Api { foreach (var job in jobs) { - KillTranscodingJob(job, delete); + KillTranscodingJob(job, deleteFiles); } } finally diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 317c4455a..bd17dc0b2 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -104,11 +104,6 @@ namespace MediaBrowser.Api.Playback.Hls } else { - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => true, false).ConfigureAwait(false); - } - // If the playlist doesn't already exist, startup ffmpeg try { diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index f01d8ddf4..5e49fbf7a 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -132,7 +132,7 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false); + await ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType.Hls && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index b7a43c237..f5a1f54cb 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -405,7 +405,7 @@ namespace MediaBrowser.Api if (!success) { - throw new UnauthorizedAccessException("Invalid user or password entered."); + throw new ArgumentException("Invalid user or password entered."); } var task = _userManager.ChangePassword(user, request.NewPassword); diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index f6651a9e3..718119fe3 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -54,11 +54,11 @@ False - ..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.dll + ..\packages\SimpleInjector.2.5.2\lib\net45\SimpleInjector.dll - + False - ..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.Diagnostics.dll + ..\packages\SimpleInjector.2.5.2\lib\net45\SimpleInjector.Diagnostics.dll diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 71d9749da..4f2a4c224 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index fb50deb66..5c3f10b54 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -319,7 +319,9 @@ + + diff --git a/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs b/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs new file mode 100644 index 000000000..f93360c64 --- /dev/null +++ b/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Sync +{ + public interface ICloudSyncProvider + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + } +} diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index 63a5d8a7c..ef4b84f27 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -60,5 +60,16 @@ namespace MediaBrowser.Controller.Sync /// The identifier. /// Task. Task CancelSchedule(string id); + + /// + /// Adds the parts. + /// + void AddParts(IEnumerable providers); + + /// + /// Gets the synchronize targets. + /// + /// IEnumerable<SyncTarget>. + IEnumerable GetSyncTargets(); } } diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs new file mode 100644 index 000000000..89f61b80e --- /dev/null +++ b/MediaBrowser.Controller/Sync/ISyncProvider.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Sync +{ + public interface ISyncProvider + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Gets the synchronize targets. + /// + /// IEnumerable<SyncTarget>. + IEnumerable GetSyncTargets(); + + /// + /// Gets the device profile. + /// + /// The target. + /// DeviceProfile. + DeviceProfile GetDeviceProfile(SyncTarget target); + } +} diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index 847d856fb..414bec715 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -1,9 +1,12 @@ -using MediaBrowser.Controller.Channels; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Providers; using MediaBrowser.Dlna.PlayTo; using MediaBrowser.Dlna.Ssdp; +using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -14,45 +17,140 @@ namespace MediaBrowser.Dlna.Channels { public class DlnaChannelFactory : IChannelFactory, IDisposable { + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + private DeviceDiscovery _deviceDiscovery; - private readonly object _syncLock = new object(); - private readonly List _servers = new List(); + private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1); + private List _servers = new List(); public static DlnaChannelFactory Instance; - public DlnaChannelFactory() + private Func> _localServersLookup; + + public DlnaChannelFactory(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger) { + _config = config; + _httpClient = httpClient; + _logger = logger; Instance = this; } - internal void Start(DeviceDiscovery deviceDiscovery) + internal void Start(DeviceDiscovery deviceDiscovery, Func> localServersLookup) { + _localServersLookup = localServersLookup; + _deviceDiscovery = deviceDiscovery; deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; + deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; } - void deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) + async void deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) { - var usn = e.Argument.Usn; - var nt = e.Argument.Nt; + string usn; + if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; - // It has to report that it's a media renderer - if (usn.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1) + string nt; + if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + string location; + if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; + + if (!IsValid(nt, usn)) + { + return; + } + + if (_localServersLookup != null) + { + if (_localServersLookup().Any(i => usn.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) + { + // Don't add the local Dlna server to this + return; + } + } + + if (GetExistingServers(usn).Any()) + { + return; + } + + await _syncLock.WaitAsync().ConfigureAwait(false); + + try + { + if (GetExistingServers(usn).Any()) + { + return; + } + + var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger).ConfigureAwait(false); + + _servers.Add(device); + } + finally + { + _syncLock.Release(); + } + } + + async void deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) + { + string usn; + if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; + + string nt; + if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; + + if (!IsValid(nt, usn)) { return; } - if (_servers.Any(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1)) + if (!GetExistingServers(usn).Any()) { return; } - lock (_syncLock) + await _syncLock.WaitAsync().ConfigureAwait(false); + + try + { + var list = _servers.ToList(); + + foreach (var device in GetExistingServers(usn).ToList()) + { + list.Remove(device); + } + + _servers = list; + } + finally + { + _syncLock.Release(); + } + } + + private bool IsValid(string nt, string usn) + { + // It has to report that it's a media renderer + if (usn.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("ContentDirectory:", StringComparison.OrdinalIgnoreCase) == -1 && + usn.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("MediaServer:", StringComparison.OrdinalIgnoreCase) == -1) { - _servers.Add(e.Argument.Device); + return false; } + + return true; + } + + private IEnumerable GetExistingServers(string usn) + { + return _servers + .Where(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1); } public IEnumerable GetChannels() @@ -65,6 +163,7 @@ namespace MediaBrowser.Dlna.Channels if (_deviceDiscovery != null) { _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered; + _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft; } } } @@ -107,7 +206,18 @@ namespace MediaBrowser.Dlna.Channels { return new InternalChannelFeatures { - + ContentTypes = new List + { + ChannelMediaContentType.Song, + ChannelMediaContentType.Clip + }, + + MediaTypes = new List + { + ChannelMediaType.Audio, + ChannelMediaType.Video, + ChannelMediaType.Photo + } }; } @@ -118,17 +228,25 @@ namespace MediaBrowser.Dlna.Channels public Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) { + // TODO: Implement return Task.FromResult(new ChannelItemResult()); } public Task GetChannelImage(ImageType type, CancellationToken cancellationToken) { - return Task.FromResult(new DynamicImageResponse()); + // TODO: Implement + return Task.FromResult(new DynamicImageResponse + { + HasImage = false + }); } public IEnumerable GetSupportedChannelImages() { - return new List(); + return new List + { + ImageType.Primary + }; } } } diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index e2772b036..8914ae8bf 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Dlna.Main private SsdpHandler _ssdpHandler; private DeviceDiscovery _deviceDiscovery; - private readonly List _registeredServerIds = new List(); + private readonly List _registeredServerIds = new List(); private bool _dlnaServerStarted; public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor) @@ -63,7 +63,7 @@ namespace MediaBrowser.Dlna.Main _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; - DlnaChannelFactory.Instance.Start(_deviceDiscovery); + DlnaChannelFactory.Instance.Start(_deviceDiscovery, () => _registeredServerIds); } void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) @@ -109,7 +109,7 @@ namespace MediaBrowser.Dlna.Main _ssdpHandler.Start(); - _deviceDiscovery = new DeviceDiscovery(_logger, _config, _httpClient, _ssdpHandler); + _deviceDiscovery = new DeviceDiscovery(_logger, _config, _httpClient, _ssdpHandler, _network); _deviceDiscovery.Start(); } @@ -174,7 +174,7 @@ namespace MediaBrowser.Dlna.Main _ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address), services); - _registeredServerIds.Add(guid); + _registeredServerIds.Add(guid.ToString("N")); } } @@ -216,8 +216,9 @@ namespace MediaBrowser.Dlna.Main _dlnaManager, _appHost, _imageProcessor, - _ssdpHandler, - _deviceDiscovery); + _deviceDiscovery, + _httpClient, + _config); _manager.Start(); } @@ -260,7 +261,7 @@ namespace MediaBrowser.Dlna.Main { try { - _ssdpHandler.UnregisterNotification(id); + _ssdpHandler.UnregisterNotification(new Guid(id)); } catch (Exception ex) { diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 045217a57..272e34a78 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -370,6 +370,8 @@ namespace MediaBrowser.Dlna.PlayTo if (_disposed) return; + const int maxSuccessiveStopReturns = 5; + try { var transportState = await GetTransportInfo().ConfigureAwait(false); @@ -408,7 +410,7 @@ namespace MediaBrowser.Dlna.PlayTo { _successiveStopCount++; - if (_successiveStopCount >= 10) + if (_successiveStopCount >= maxSuccessiveStopReturns) { RestartTimerInactive(); } @@ -423,6 +425,13 @@ namespace MediaBrowser.Dlna.PlayTo catch (Exception ex) { _logger.ErrorException("Error updating device info", ex); + + _successiveStopCount++; + + if (_successiveStopCount >= maxSuccessiveStopReturns) + { + RestartTimerInactive(); + } } } @@ -828,17 +837,15 @@ namespace MediaBrowser.Dlna.PlayTo } } + var device = new Device(deviceProperties, httpClient, logger, config); + if (isRenderer) { - var device = new Device(deviceProperties, httpClient, logger, config); - await device.GetRenderingProtocolAsync().ConfigureAwait(false); await device.GetAVProtocolAsync().ConfigureAwait(false); - - return device; } - return null; + return device; } #endregion diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 34f657217..eb4eb3889 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -1,7 +1,6 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; @@ -34,7 +33,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IUserManager _userManager; private readonly IImageProcessor _imageProcessor; - private readonly SsdpHandler _ssdpHandler; + private readonly DeviceDiscovery _deviceDiscovery; private readonly string _serverAddress; public bool IsSessionActive @@ -52,7 +51,7 @@ namespace MediaBrowser.Dlna.PlayTo private Timer _updateTimer; - public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, SsdpHandler ssdpHandler, string serverAddress) + public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery) { _session = session; _itemRepository = itemRepository; @@ -61,8 +60,8 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager = dlnaManager; _userManager = userManager; _imageProcessor = imageProcessor; - _ssdpHandler = ssdpHandler; _serverAddress = serverAddress; + _deviceDiscovery = deviceDiscovery; _logger = logger; } @@ -75,33 +74,12 @@ namespace MediaBrowser.Dlna.PlayTo _device.MediaChanged += _device_MediaChanged; _device.Start(); - _ssdpHandler.MessageReceived += _SsdpHandler_MessageReceived; + _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft; _updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000); } - private void updateTimer_Elapsed(object state) - { - if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120)) - { - try - { - // Session is inactive, mark it for Disposal and don't start the elapsed timer. - _sessionManager.ReportSessionEnded(_session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ReportSessionEnded", ex); - } - } - } - - private string GetServerAddress() - { - return _serverAddress; - } - - void _SsdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) { string nts; e.Headers.TryGetValue("NTS", out nts); @@ -112,9 +90,7 @@ namespace MediaBrowser.Dlna.PlayTo string nt; if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; - if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && - String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && - usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && + if ( usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && !_disposed) { if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 || @@ -132,6 +108,27 @@ namespace MediaBrowser.Dlna.PlayTo } } + private void updateTimer_Elapsed(object state) + { + if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120)) + { + try + { + // Session is inactive, mark it for Disposal and don't start the elapsed timer. + _sessionManager.ReportSessionEnded(_session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error in ReportSessionEnded", ex); + } + } + } + + private string GetServerAddress() + { + return _serverAddress; + } + async void _device_MediaChanged(object sender, MediaChangedEventArgs e) { try @@ -626,7 +623,7 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackProgress -= _device_PlaybackProgress; _device.PlaybackStopped -= _device_PlaybackStopped; _device.MediaChanged -= _device_MediaChanged; - _ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived; + _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; DisposeUpdateTimer(); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 165f788e8..cea397cae 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; @@ -26,11 +28,12 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IServerApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; + private readonly IHttpClient _httpClient; + private readonly IServerConfigurationManager _config; - private readonly SsdpHandler _ssdpHandler; private readonly DeviceDiscovery _deviceDiscovery; - public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, SsdpHandler ssdpHandler, DeviceDiscovery deviceDiscovery) + public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config) { _logger = logger; _sessionManager = sessionManager; @@ -40,8 +43,9 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager = dlnaManager; _appHost = appHost; _imageProcessor = imageProcessor; - _ssdpHandler = ssdpHandler; _deviceDiscovery = deviceDiscovery; + _httpClient = httpClient; + _config = config; } public void Start() @@ -49,14 +53,19 @@ namespace MediaBrowser.Dlna.PlayTo _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) + async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) { - var device = e.Argument.Device; - var localIp = e.Argument.LocalIpAddress; + var localIp = e.LocalIp; - var usn = e.Argument.Usn; - var nt = e.Argument.Nt; + string usn; + if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + string nt; + if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + string location; + if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; + // It has to report that it's a media renderer if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) @@ -67,12 +76,14 @@ namespace MediaBrowser.Dlna.PlayTo if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1)) { return; - } + } + + var uri = new Uri(location); + + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); if (device.RendererCommands != null) { - var uri = e.Argument.Uri; - var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null) .ConfigureAwait(false); @@ -90,8 +101,8 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager, _userManager, _imageProcessor, - _ssdpHandler, - serverAddress); + serverAddress, + _deviceDiscovery); controller.Init(device); diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs index aca164092..8cda73082 100644 --- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs +++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs @@ -25,10 +25,12 @@ namespace MediaBrowser.Dlna.Ssdp private readonly SsdpHandler _ssdpHandler; private readonly CancellationTokenSource _tokenSource; private readonly IHttpClient _httpClient; + private readonly INetworkManager _networkManager; - public event EventHandler> DeviceDiscovered; + public event EventHandler DeviceDiscovered; + public event EventHandler DeviceLeft; - public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IHttpClient httpClient, SsdpHandler ssdpHandler) + public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IHttpClient httpClient, SsdpHandler ssdpHandler, INetworkManager networkManager) { _tokenSource = new CancellationTokenSource(); @@ -36,11 +38,12 @@ namespace MediaBrowser.Dlna.Ssdp _config = config; _httpClient = httpClient; _ssdpHandler = ssdpHandler; + _networkManager = networkManager; } public void Start() { - //_ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived; + _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived; foreach (var network in GetNetworkInterfaces()) { @@ -72,8 +75,30 @@ namespace MediaBrowser.Dlna.Ssdp } } - void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) + void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) { + string nts; + e.Headers.TryGetValue("NTS", out nts); + + if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && + String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && + !_disposed) + { + EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger); + return; + } + + try + { + //TryCreateDevice(e, IPAddress.Parse(_networkManager.GetLocalIpAddresses().First())); + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + _logger.ErrorException("Error creating play to controller", ex); + } } private IEnumerable GetNetworkInterfaces() @@ -113,8 +138,9 @@ namespace MediaBrowser.Dlna.Ssdp if (receivedBytes > 0) { var args = SsdpHelper.ParseSsdpResponse(receiveBuffer, endPoint); + args.LocalIp = localIp; - TryCreateDevice(args, localIp); + TryCreateDevice(args); } } @@ -197,7 +223,7 @@ namespace MediaBrowser.Dlna.Ssdp return socket; } - private async void TryCreateDevice(SsdpMessageEventArgs args, IPAddress localIp) + private void TryCreateDevice(SsdpMessageEventArgs args) { string nts; args.Headers.TryGetValue("NTS", out nts); @@ -230,38 +256,7 @@ namespace MediaBrowser.Dlna.Ssdp _logger.Debug("{0} Device message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText); } - try - { - await TryCreateDevice(new Uri(location), localIp, usn, nt).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error creating play to controller", ex); - } - } - - private async Task TryCreateDevice(Uri uri, IPAddress localIp, string usn, string nt) - { - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); - - if (device != null) - { - EventHelper.FireEventIfNotNull(DeviceDiscovered, this, new GenericEventArgs - { - Argument = new DeviceDiscoveryInfo - { - Device = device, - LocalIpAddress = localIp, - Uri = uri, - Usn = usn, - Nt = nt - } - - }, _logger); - } + EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); } public void Dispose() diff --git a/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs b/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs index d6368191b..189476830 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs @@ -12,6 +12,8 @@ namespace MediaBrowser.Dlna.Ssdp public Dictionary Headers { get; set; } + public IPAddress LocalIp { get; set; } + public SsdpMessageEventArgs() { Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index a5ef7977b..5d7762823 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.LocalMetadata.Images if (item.SupportsLocalMetadata) { // Episode has it's own provider - if (item.IsOwnedItem || item is Episode || item is Audio) + if (item.IsOwnedItem || item is Episode || item is Audio || item is Photo) { return false; } diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index c43875b04..1a2c341da 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -63,6 +63,8 @@ namespace MediaBrowser.LocalMetadata.Savers // Deprecated. No longer saving in this field. "MPAARating", + "MPAADescription", + "MusicBrainzArtistId", "MusicBrainzAlbumArtistId", "MusicBrainzAlbumId", @@ -230,6 +232,11 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); } + if (!string.IsNullOrEmpty(item.OfficialRatingDescription)) + { + builder.Append("" + SecurityElement.Escape(item.OfficialRatingDescription) + ""); + } + builder.Append("" + SecurityElement.Escape(item.DateCreated.ToLocalTime().ToString("G")) + ""); builder.Append("" + item.IsLocked.ToString().ToLower() + ""); diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index c33e00944..17463941c 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -869,6 +869,9 @@ Sync\SyncScheduleRequest.cs + + Sync\SyncTarget.cs + System\LogFile.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 22eb008d1..9dafe0cef 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -826,6 +826,9 @@ Sync\SyncScheduleRequest.cs + + Sync\SyncTarget.cs + System\LogFile.cs diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index fe05483e7..f525ae5e0 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Extensions; using System.Diagnostics; -using System.Runtime.Serialization; namespace MediaBrowser.Model.Entities { @@ -138,8 +137,11 @@ namespace MediaBrowser.Model.Entities string codec = Codec ?? string.Empty; + // sub = external .sub file + return StringHelper.IndexOfIgnoreCase(codec, "pgs") == -1 && - StringHelper.IndexOfIgnoreCase(codec, "dvd") == -1; + StringHelper.IndexOfIgnoreCase(codec, "dvd") == -1 && + !StringHelper.EqualsIgnoreCase(codec, "sub"); } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index a2f2b1318..3c265ff91 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -304,6 +304,7 @@ + diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 09049a196..34809834c 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Sync /// Gets or sets the device identifier. /// /// The device identifier. - public string DeviceId { get; set; } + public string TargetId { get; set; } /// /// Gets or sets the item identifier. /// diff --git a/MediaBrowser.Model/Sync/SyncJobRequest.cs b/MediaBrowser.Model/Sync/SyncJobRequest.cs index 9871f976d..42b744db6 100644 --- a/MediaBrowser.Model/Sync/SyncJobRequest.cs +++ b/MediaBrowser.Model/Sync/SyncJobRequest.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Sync /// Gets or sets the device identifier. /// /// The device identifier. - public List DeviceIds { get; set; } + public List TargetIds { get; set; } /// /// Gets or sets the item identifier. /// @@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Sync public SyncJobRequest() { - DeviceIds = new List(); + TargetIds = new List(); } } } diff --git a/MediaBrowser.Model/Sync/SyncScheduleRequest.cs b/MediaBrowser.Model/Sync/SyncScheduleRequest.cs index e1ba4b115..076548f57 100644 --- a/MediaBrowser.Model/Sync/SyncScheduleRequest.cs +++ b/MediaBrowser.Model/Sync/SyncScheduleRequest.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Sync /// Gets or sets the device identifier. /// /// The device identifier. - public List DeviceIds { get; set; } + public List TargetIds { get; set; } /// /// Gets or sets the quality. /// @@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Sync public SyncScheduleRequest() { - DeviceIds = new List(); + TargetIds = new List(); } } } diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs new file mode 100644 index 000000000..8901f0f27 --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncTarget.cs @@ -0,0 +1,17 @@ + +namespace MediaBrowser.Model.Sync +{ + public class SyncTarget + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index f530564ea..94715b490 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -76,7 +76,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer {typeof (InvalidOperationException), 422}, {typeof (ResourceNotFoundException), 404}, {typeof (FileNotFoundException), 404}, - {typeof (DirectoryNotFoundException), 404} + {typeof (DirectoryNotFoundException), 404}, + {typeof (Implementations.Security.AuthenticationException), 401} }; HostConfig.Instance.DebugMode = true; diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 1933cc716..ebb5ab741 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security if (user != null && user.Configuration.IsDisabled) { - throw new UnauthorizedAccessException("User account has been disabled."); + throw new AuthenticationException("User account has been disabled."); } if (!string.IsNullOrWhiteSpace(auth.DeviceId) && diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 1e0bbc39b..dbe020908 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -17,6 +17,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Server.Implementations.Security; namespace MediaBrowser.Server.Implementations.Library { @@ -137,7 +138,7 @@ namespace MediaBrowser.Server.Implementations.Library if (user.Configuration.IsDisabled) { - throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); } var existingPasswordString = string.IsNullOrEmpty(user.Password) ? GetSha1String(string.Empty) : user.Password; diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index 1192bafb7..4b2e7ba1d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 37a5735e4..d3bdf0bbb 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 92f352ca6..a853a39eb 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -125,7 +125,7 @@ "HeaderLatestMedia": "Latest Media", "ButtonMore": "V\u00edce...", "HeaderFavoriteMovies": "Obl\u00edben\u00e9 filmy", - "HeaderFavoriteShows": "Favorite Shows", + "HeaderFavoriteShows": "Obl\u00edben\u00e9 seri\u00e1ly", "HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteGames": "Favorite Games", "HeaderRatingsDownloads": "Rating \/ Downloads", @@ -208,7 +208,7 @@ "MessageEnsureOpenTuner": "Please ensure there is an open tuner availalble.", "ButtonHome": "Dom\u016f", "ButtonDashboard": "Dashboard", - "ButtonReports": "Reports", + "ButtonReports": "Hl\u00e1\u0161en\u00ed", "ButtonMetadataManager": "Metadata Manager", "HeaderTime": "Time", "HeaderName": "N\u00e1zev", @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Vybrat", - "ButtonNew": "Nov\u00e9" + "ButtonNew": "Nov\u00e9", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 988b03650..8c13b22b7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "V\u00e6lg", - "ButtonNew": "Ny" + "ButtonNew": "Ny", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 58b273c26..c871cae0a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Ausw\u00e4hlen", - "ButtonNew": "Neu" + "ButtonNew": "Neu", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 9ad65edbd..3514a060a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 03694cad0..da4787718 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 bfd93b795..ff77db14b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 05647f10d..f5f30360d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Seleccionar", - "ButtonNew": "Nuevo" + "ButtonNew": "Nuevo", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 62ecc2578..53047104c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Seleccionar", - "ButtonNew": "Nuevo" + "ButtonNew": "Nuevo", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 e0b199b12..6bcb5aed5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -30,7 +30,7 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Merci. Votre cl\u00e9 de supporteur a \u00e9t\u00e9 mise \u00e0 jour.", "MessageKeyRemoved": "Merci, Votre cl\u00e9 de supporteur a \u00e9t\u00e9 supprim\u00e9e.", - "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", + "ErrorLaunchingChromecast": "Une erreur est rencontr\u00e9e lors du lancement de Chromecast. Assurez-vous bien que votre p\u00e9riph\u00e9rique est bien connect\u00e9 \u00e0 votre r\u00e9seau sans-fil.", "HeaderSearch": "Recherche", "LabelArtist": "Artiste", "LabelMovie": "Film", @@ -104,7 +104,7 @@ "LabelVersionUpToDate": "\u00c0 jour!", "ButtonResetTuner": "Red\u00e9marrer tuner", "HeaderResetTuner": "Red\u00e9marrer tuner", - "MessageConfirmResetTuner": "Are you sure you wish to reset this tuner? Any active players or recordings will be abruptly stopped.", + "MessageConfirmResetTuner": "\u00cates-vous s\u00fbr de vouloir r\u00e9initialiser ce tuner ? Tout les lecteurs ou enregistrements actifs seront brusquement interrompus.", "ButtonCancelSeries": "Annuler s\u00e9ries", "HeaderSeriesRecordings": "Enregistrements de s\u00e9ries", "LabelAnytime": "N'importe quelle heure", @@ -112,8 +112,8 @@ "StatusWatching": "En lecture", "StatusRecordingProgram": "Enregistre {0}", "StatusWatchingProgram": "En lecture de {0}", - "HeaderSplitMedia": "Split Media Apart", - "MessageConfirmSplitMedia": "Are you sure you wish to split the media sources into separate items?", + "HeaderSplitMedia": "S\u00e9parer les m\u00e9dias", + "MessageConfirmSplitMedia": "\u00cates vous s\u00fbrs de vouloir diviser les sources de m\u00e9dia dans des items s\u00e9par\u00e9s ?", "HeaderError": "Erreur", "MessagePleaseSelectOneItem": "Veuillez s\u00e9lectionner au moins un item.", "MessagePleaseSelectTwoItems": "Veuillez s\u00e9lectionner au moins deux items.", @@ -136,16 +136,16 @@ "HeaderSelectImagesByNamePath": "S\u00e9lectionner le chemin d'acc\u00e8s du \"Images By Name\"", "HeaderSelectMetadataPath": "S\u00e9lectionner le chemin d'acc\u00e8s des m\u00e9tadonn\u00e9es", "HeaderSelectServerCachePathHelp": "Parcourez ou entrez le chemin \u00e0 utiliser pour les fichiers caches du serveur. Le dossier doit \u00eatre accessible en \u00e9criture. L'emplacement de ce fichier aura un impact direct sur les performances du serveur et devrait \u00eatre plac\u00e9 id\u00e9alement sur un SSD.", - "HeaderSelectTranscodingPathHelp": "Browse or enter the path to use for transcoding temporary files. The folder must be writeable.", - "HeaderSelectImagesByNamePathHelp": "Browse or enter the path to your items by name folder. The folder must be writeable.", - "HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.", - "HeaderSelectChannelDownloadPath": "Select Channel Download Path", - "HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable.", + "HeaderSelectTranscodingPathHelp": "Parcourir ou saisir le chemin dossier \u00e0 utiliser pour le transcodage des fichiers temporaires. Le dossier devra \u00eatre accessible en \u00e9criture.", + "HeaderSelectImagesByNamePathHelp": "Parcourir ou saisir le chemin dossier de vos items par le nom du dossier. Le dossier devra \u00eatre accessible en \u00e9criture.", + "HeaderSelectMetadataPathHelp": "Parcourir ou saisir le chemin o\u00f9 vous aimeriez stocker les m\u00e9tadonn\u00e9es. Le r\u00e9pertoire doit \u00eatre accessible en \u00e9criture.", + "HeaderSelectChannelDownloadPath": "S\u00e9lectionnez le chemin de t\u00e9l\u00e9chargement des cha\u00eenes.", + "HeaderSelectChannelDownloadPathHelp": "Parcourir ou saisir le chemin destin\u00e9 au stockage des fichers cache des cha\u00eenes. Le r\u00e9pertoire doit \u00eatre accessible en \u00e9criture.", "OptionNewCollection": "Nouveau...", "ButtonAdd": "Ajouter", "ButtonRemove": "Supprimer", "LabelChapterDownloaders": "Agents de t\u00e9l\u00e9chargement de chapitres:", - "LabelChapterDownloadersHelp": "Enable and rank your preferred chapter downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information.", + "LabelChapterDownloadersHelp": "Activer et classer vos sources de t\u00e9l\u00e9chargement de chapitre pr\u00e9f\u00e9r\u00e9s dans un ordre de priorit\u00e9. Les sources de t\u00e9l\u00e9chargement ayant une priorit\u00e9 basse, seront seulement utilis\u00e9es pour remplir les informations manquantes.", "HeaderFavoriteAlbums": "Albums favoris", "HeaderLatestChannelMedia": "Derniers items de cha\u00eenes", "ButtonOrganizeFile": "Organiser fichier", @@ -157,11 +157,11 @@ "StatusSuccess": "Succ\u00e8s", "MessageFileWillBeDeleted": "Le fichier suivant sera supprim\u00e9:", "MessageSureYouWishToProceed": "\u00cates-vous s\u00fbr de vouloir continuer?", - "MessageDuplicatesWillBeDeleted": "In addition the following dupliates will be deleted:", + "MessageDuplicatesWillBeDeleted": "De plus, les doubles suivants vont \u00eatre supprim\u00e9s :", "MessageFollowingFileWillBeMovedFrom": "Le fichier suivant sera d\u00e9plac\u00e9 de:", "MessageDestinationTo": "Vers:", "HeaderSelectWatchFolder": "S\u00e9lectionner le r\u00e9pertoire surveill\u00e9", - "HeaderSelectWatchFolderHelp": "Browse or enter the path to your watch folder. The folder must be writeable.", + "HeaderSelectWatchFolderHelp": "Parcourir ou saisir le chemin de votre r\u00e9pertoire de surveillance. Le r\u00e9pertoire doit \u00eatre accessible en \u00e9criture.", "OrganizePatternResult": "R\u00e9sultats: {0}", "HeaderRestart": "Red\u00e9marrer", "HeaderShutdown": "\u00c9teindre", @@ -191,131 +191,133 @@ "ButtonCancel": "Annuler", "ButtonRefresh": "Actualiser", "LabelCurrentPath": "Chemin actuel:", - "HeaderSelectMediaPath": "Select Media Path", + "HeaderSelectMediaPath": "S\u00e9lectionner le chemin m\u00e9dia", "ButtonNetwork": "R\u00e9seau", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "MessageDirectoryPickerInstruction": "Les chemins r\u00e9seaux seront saisis manuellement dans le cas o\u00f9 le bouton R\u00e9seau ne parvient pas \u00e0 rechercher vos p\u00e9riph\u00e9riques. Par exmple, {0} ou {1}.", "HeaderMenu": "Menu", - "ButtonOpen": "Open", - "ButtonOpenInNewTab": "Open in new tab", - "ButtonShuffle": "Shuffle", - "ButtonInstantMix": "Instant mix", - "ButtonResume": "Resume", + "ButtonOpen": "Ouvrir", + "ButtonOpenInNewTab": "Ouvrir dans un nouvel onglet", + "ButtonShuffle": "M\u00e9langer", + "ButtonInstantMix": "Instantan\u00e9", + "ButtonResume": "Reprendre", "HeaderScenes": "Sc\u00e8nes", - "HeaderAudioTracks": "Audio Tracks", - "HeaderSubtitles": "Subtitles", - "HeaderVideoQuality": "Video Quality", - "MessageErrorPlayingVideo": "There was an error playing the video.", - "MessageEnsureOpenTuner": "Please ensure there is an open tuner availalble.", + "HeaderAudioTracks": "Pistes audio", + "HeaderSubtitles": "Sous-titres", + "HeaderVideoQuality": "Qualit\u00e9 vid\u00e9o", + "MessageErrorPlayingVideo": "La lecture de la vid\u00e9o a rencontr\u00e9 une erreur", + "MessageEnsureOpenTuner": "V\u00e9rifiez bien que le tuner est disponible", "ButtonHome": "Principal", - "ButtonDashboard": "Dashboard", - "ButtonReports": "Reports", - "ButtonMetadataManager": "Metadata Manager", - "HeaderTime": "Time", + "ButtonDashboard": "Tableau de bord", + "ButtonReports": "Rapports", + "ButtonMetadataManager": "Gestionnaire de m\u00e9tadonn\u00e9es", + "HeaderTime": "Heure", "HeaderName": "Nom", "HeaderAlbum": "Album", - "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist", - "LabelAddedOnDate": "Added {0}", - "ButtonStart": "Start", + "HeaderAlbumArtist": "Artiste de l'album", + "HeaderArtist": "Artiste", + "LabelAddedOnDate": "Ajout\u00e9 {0}", + "ButtonStart": "Commencer", "HeaderChannels": "Cha\u00eenes", "HeaderMediaFolders": "R\u00e9pertoires de m\u00e9dias", - "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", - "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.", + "HeaderBlockItemsWithNoRating": "Bloquer les items ne comportant aucune information de classement :", + "OptionBlockOthers": "Autres", + "OptionBlockTvShows": "Emissions TV", + "OptionBlockTrailers": "Bande-annonces", + "OptionBlockMusic": "Musiques", + "OptionBlockMovies": "Films", + "OptionBlockBooks": "Livres", + "OptionBlockGames": "Jeux", + "OptionBlockLiveTvPrograms": "Programmes TV en direct", + "OptionBlockLiveTvChannels": "Cha\u00eenes TV en direct", + "OptionBlockChannelContent": "Cha\u00eenes Internet", + "ButtonRevoke": "R\u00e9voquer", + "MessageConfirmRevokeApiKey": "\u00cates-vous s\u00fbr de vouloir r\u00e9voquer la cl\u00e9 API ? La connexion \u00e0 l'application Media Browser sera brusquement interrompue.", + "HeaderConfirmRevokeApiKey": "R\u00e9voquer la cl\u00e9 API", + "ValueContainer": "Conteneur : {0}", + "ValueAudioCodec": "Codec Audio : {0}", + "ValueVideoCodec": "Codec Vid\u00e9o : {0}", + "ValueCodec": "Codec : {0}", + "ValueConditions": "Conditions : {0}", + "LabelAll": "Tout", + "HeaderDeleteImage": "Supprimer l'image", + "MessageFileNotFound": "Fichier introuvable.", + "MessageFileReadError": "Une erreur a \u00e9t\u00e9 rencontr\u00e9e pendant la lecture de ce fichier.", + "ButtonNextPage": "Page suivante", + "ButtonPreviousPage": "Page pr\u00e9c\u00e9dente", + "ButtonMoveLeft": "D\u00e9placer \u00e0 gauche", + "ButtonMoveRight": "D\u00e9placer \u00e0 droite", + "ButtonBrowseOnlineImages": "Parcourir les images en ligne", + "HeaderDeleteItem": "Supprimer l'item", + "ConfirmDeleteItem": "\u00cates-vous s\u00fbr de vouloir supprimer cet item de votre biblioth\u00e8que ?", + "MessagePleaseEnterNameOrId": "Merci de saisir un nom ou un ID externe.", + "MessageValueNotCorrect": "La valeur saisie est incorrecte. Merci de r\u00e9\u00e9ssayer.", + "MessageItemSaved": "Item sauvegard\u00e9.", "OptionEnded": "Termin\u00e9", "OptionContinuing": "En cours", "OptionOff": "Off", "OptionOn": "On", - "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", + "HeaderFields": "Champs", + "HeaderFieldsHelp": "Glisser un champ sur \"off\" pour le v\u00e9rouiller et emp\u00eacher les modifications de donn\u00e9es.", + "HeaderLiveTV": "TV en direct", + "MissingLocalTrailer": "Bande-annonce locale manquante.", + "MissingPrimaryImage": "Image principale manquante.", + "MissingBackdropImage": "Image d'arri\u00e8re-plan manquante.", + "MissingLogoImage": "Image logo manquante.", + "MissingEpisode": "Episode manquant.", + "OptionScreenshots": "Captures d'\u00e9cran", + "OptionBackdrops": "Arri\u00e8re-plans", "OptionImages": "Images", - "OptionKeywords": "Keywords", + "OptionKeywords": "Mots-cl\u00e9s", "OptionTags": "Tags", "OptionStudios": "Studios", - "OptionName": "Name", - "OptionOverview": "Overview", + "OptionName": "Nom", + "OptionOverview": "Aper\u00e7u", "OptionGenres": "Genres", "OptionParentalRating": "Note d'\u00e9valuation de contr\u00f4le parental", "OptionPeople": "People", "OptionRuntime": "Dur\u00e9e", - "OptionProductionLocations": "Production Locations", - "OptionBirthLocation": "Birth Location", + "OptionProductionLocations": "Sites de production", + "OptionBirthLocation": "Lieu de naissance", "LabelAllChannels": "Toutes les cha\u00eenes", - "LabelLiveProgram": "LIVE", - "LabelNewProgram": "NEW", + "LabelLiveProgram": "DIRECT", + "LabelNewProgram": "NOUVEAUTE", "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.", + "HeaderChangeFolderType": "Modifier le type de dossier", + "HeaderChangeFolderTypeHelp": "Pour modifier le type de dossier, merci de supprimer et reconstruire la collection avec le nouveau type.", + "HeaderAlert": "Alerte", + "MessagePleaseRestart": "Merci de red\u00e9marrer pour achever la mise \u00e0 jour.", "ButtonRestart": "Red\u00e9marrer", - "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": "Merci de rafra\u00eechir cette page pour recevoir les nouvelles mises \u00e0 jour du serveur.", + "ButtonHide": "Cacher", + "MessageSettingsSaved": "Param\u00e8tres sauvegard\u00e9s.", + "ButtonSignOut": "Se d\u00e9connecter", + "ButtonMyProfile": "Mon profil", + "ButtonMyPreferences": "Mes pr\u00e9f\u00e9rences", + "MessageBrowserDoesNotSupportWebSockets": "Ce navigateur ne supporte pas les sockets Web. Pour un meilleur confort d'utilisation, essayez avec un navigateur moderne tels que Chrome, Firefox, IE10+, Safari (iOS) ou Opera.", + "LabelInstallingPackage": "Installation de {0}", + "LabelPackageInstallCompleted": "L'installation de {0} est termin\u00e9e.", + "LabelPackageInstallFailed": "L'installation de {0} a \u00e9chou\u00e9.", + "LabelPackageInstallCancelled": "L'installation de {0} a \u00e9t\u00e9 annul\u00e9e.", "TabServer": "Serveur", - "TabUsers": "Users", - "TabLibrary": "Library", + "TabUsers": "Utilisateurs", + "TabLibrary": "Biblioth\u00e8que", "TabMetadata": "M\u00e9tadonn\u00e9es", "TabDLNA": "DLNA", - "TabLiveTV": "Live TV", - "TabAutoOrganize": "Auto-Organize", + "TabLiveTV": "TV en direct", + "TabAutoOrganize": "Auto-Organiser", "TabPlugins": "Plugins", "TabAdvanced": "Avanc\u00e9", - "TabHelp": "Help", - "TabScheduledTasks": "Scheduled Tasks", - "ButtonFullscreen": "Fullscreen", - "ButtonAudioTracks": "Audio Tracks", + "TabHelp": "Aide", + "TabScheduledTasks": "T\u00e2ches planifi\u00e9es", + "ButtonFullscreen": "Plein \u00e9cran", + "ButtonAudioTracks": "Pistes audio", "ButtonSubtitles": "Sous-titres", "ButtonScenes": "Sc\u00e8nes", - "ButtonQuality": "Quality", + "ButtonQuality": "Qualit\u00e9", "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderSelectPlayer": "S\u00e9lectionner le lecteur :", "ButtonSelect": "S\u00e9lectionner", - "ButtonNew": "Nouveau" + "ButtonNew": "Nouveau", + "MessageInternetExplorerWebm": "Pour de meilleurs r\u00e9sultats avec Internet Explorer, merci d'installer le plugin WebM pour IE.", + "HeaderVideoError": "Erreur vid\u00e9o" } \ 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 2cb25ef9b..d5202486f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "\u05d1\u05d7\u05e8", - "ButtonNew": "\u05d7\u05d3\u05e9" + "ButtonNew": "\u05d7\u05d3\u05e9", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 1963f249a..4cf04f2a7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -58,14 +58,14 @@ "ButtonMute": "Muto", "ButtonUnmute": "Togli muto", "ButtonStop": "Stop", - "ButtonNextTrack": "Prossimo", + "ButtonNextTrack": "traccia Prossima", "ButtonPause": "Pausa", "ButtonPlay": "Riproduci", "ButtonEdit": "Modifica", "ButtonQueue": "In coda", - "ButtonPlayTrailer": "Visualizza trailer", + "ButtonPlayTrailer": "Visualizza Trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Precedente", + "ButtonPreviousTrack": "Precedente traccia", "LabelEnabled": "Abilitato", "LabelDisabled": "Disabilitato", "ButtonMoreInformation": "Maggiori informazioni", @@ -121,7 +121,7 @@ "MessageConfirmItemGrouping": "Clienti Media Browser sceglier\u00e0 automaticamente la versione ottimale per giocare sulla base del dispositivo e prestazioni di rete. Sei sicuro di voler continuare?", "HeaderResume": "Riprendi", "HeaderMyViews": "Mie viste", - "HeaderLibraryFolders": "Vista", + "HeaderLibraryFolders": "Cartelle dei mediata", "HeaderLatestMedia": "Ultimi Media", "ButtonMore": "Pi\u00f9 info...", "HeaderFavoriteMovies": "Film preferiti", @@ -215,7 +215,7 @@ "HeaderAlbum": "Album", "HeaderAlbumArtist": "Artista Album", "HeaderArtist": "Artista", - "LabelAddedOnDate": "Added {0}", + "LabelAddedOnDate": "Aggiunto {0}", "ButtonStart": "Avvio", "HeaderChannels": "Canali", "HeaderMediaFolders": "Cartelle dei media", @@ -233,89 +233,91 @@ "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}", + "ValueContainer": "Contenitore: {0}", "ValueAudioCodec": "Audio Codec: {0}", "ValueVideoCodec": "Video Codec: {0}", "ValueCodec": "Codec: {0}", - "ValueConditions": "Conditions: {0}", + "ValueConditions": "Condizioni: {0}", "LabelAll": "Tutti", "HeaderDeleteImage": "Cancella Immagine", "MessageFileNotFound": "File non trovato.", - "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.", + "MessageFileReadError": "Errore leggendo questo file", + "ButtonNextPage": "Prossima pagina", + "ButtonPreviousPage": "Pagina precedente", + "ButtonMoveLeft": "Muovi a sinistra", + "ButtonMoveRight": "Muovi a destra", + "ButtonBrowseOnlineImages": "Sfoglia le immagini Online", + "HeaderDeleteItem": "Elimina elemento", + "ConfirmDeleteItem": "Sei sicuro di voler eliminare questo elemento dalla tua libreria?", + "MessagePleaseEnterNameOrId": "Inserisci il nome o id esterno.", + "MessageValueNotCorrect": "Il valore inserito non \u00e8 corretto.Riprova di nuovo.", + "MessageItemSaved": "Elemento salvato.", "OptionEnded": "Finito", "OptionContinuing": "In corso", "OptionOff": "Off", "OptionOn": "On", - "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.", + "HeaderFields": "Campi", + "HeaderFieldsHelp": "Far scorrere un campo a 'off' per bloccarlo e impedire che sia i dati vengano modificati.", + "HeaderLiveTV": "Tv in diretta", + "MissingLocalTrailer": "Trailer locali mancanti", + "MissingPrimaryImage": "Immagini principali locali mancanti", + "MissingBackdropImage": "Sfondi mancanti", + "MissingLogoImage": "Loghi mancanti", + "MissingEpisode": "Episodi mancanti", "OptionScreenshots": "Screenshots", - "OptionBackdrops": "Backdrops", - "OptionImages": "Images", - "OptionKeywords": "Keywords", + "OptionBackdrops": "Sfondi", + "OptionImages": "Immagini", + "OptionKeywords": "Parole", "OptionTags": "Tags", "OptionStudios": "Studios", - "OptionName": "Name", - "OptionOverview": "Overview", - "OptionGenres": "Genres", + "OptionName": "Nome", + "OptionOverview": "Panoramica", + "OptionGenres": "Generi", "OptionParentalRating": "Voto Genitori", - "OptionPeople": "People", + "OptionPeople": "Persone", "OptionRuntime": "Durata", - "OptionProductionLocations": "Production Locations", - "OptionBirthLocation": "Birth Location", + "OptionProductionLocations": "Sedi di produzione", + "OptionBirthLocation": "Nascita Posizione", "LabelAllChannels": "Tutti i canali", "LabelLiveProgram": "LIVE", - "LabelNewProgram": "NEW", + "LabelNewProgram": "Nuovo", "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.", + "HeaderChangeFolderType": "Cambia il tipo di cartella", + "HeaderChangeFolderTypeHelp": "Per cambiare il tipo di cartella, rimuovere e ricostruire la collezione con il nuovo tipo.", + "HeaderAlert": "Avviso", + "MessagePleaseRestart": "Si prega di riavviare per completare l'aggiornamento.", "ButtonRestart": "Riavvia", - "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": "Si prega di aggiornare questa pagina per ricevere i nuovi aggiornamenti dal server.", + "ButtonHide": "Nascondi", + "MessageSettingsSaved": "Settaggi salvati.", + "ButtonSignOut": "Esci", + "ButtonMyProfile": "Mio Profilo", + "ButtonMyPreferences": "Mie preferenze", + "MessageBrowserDoesNotSupportWebSockets": "Questo browser non supporta i socket web. Per una migliore esperienza, provare un browser pi\u00f9 recente come Chrome, Firefox, IE10 +, Safari (iOS) o Opera.", + "LabelInstallingPackage": "Installazione di {0}", + "LabelPackageInstallCompleted": "{0} completamento dell'installazione.", + "LabelPackageInstallFailed": "{0} installazione non \u00e8 riuscita.", + "LabelPackageInstallCancelled": "{0} installazione annullata.", "TabServer": "Server", - "TabUsers": "Users", - "TabLibrary": "Library", + "TabUsers": "Utenti", + "TabLibrary": "Librerie", "TabMetadata": "Metadata", "TabDLNA": "DLNA", - "TabLiveTV": "Live TV", - "TabAutoOrganize": "Auto-Organize", + "TabLiveTV": "Tv indiretta", + "TabAutoOrganize": "Organizza Automaticamente", "TabPlugins": "Plugins", "TabAdvanced": "Avanzato", - "TabHelp": "Help", - "TabScheduledTasks": "Scheduled Tasks", - "ButtonFullscreen": "Fullscreen", - "ButtonAudioTracks": "Audio Tracks", + "TabHelp": "Aiuto", + "TabScheduledTasks": "Operazioni pianificate", + "ButtonFullscreen": "Tutto schermo", + "ButtonAudioTracks": "Tracce audio", "ButtonSubtitles": "Sottotitoli", "ButtonScenes": "Scene", - "ButtonQuality": "Quality", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "ButtonQuality": "Qualit\u00e0", + "HeaderNotifications": "Notifiche", + "HeaderSelectPlayer": "Utente selezionato:", "ButtonSelect": "Seleziona", - "ButtonNew": "Nuovo" + "ButtonNew": "Nuovo", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 0b6e7c9c5..4c17357ff 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -323,5 +323,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json index 113389a11..083f02665 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -306,7 +306,7 @@ "TabLiveTV": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414", "TabAutoOrganize": "\u0410\u0432\u0442\u043e\u04b1\u0439\u044b\u043c\u0434\u0430\u0441\u0442\u044b\u0440\u0443", "TabPlugins": "\u041f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440", - "TabAdvanced": "\u041a\u0435\u04a3\u0435\u0439\u0442\u0456\u043b\u0433\u0435\u043d\u0434\u0435\u0440", + "TabAdvanced": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430", "TabHelp": "\u0410\u043d\u044b\u049b\u0442\u0430\u043c\u0430", "TabScheduledTasks": "\u0416\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0443\u0448\u044b", "ButtonFullscreen": "\u0422\u043e\u043b\u044b\u049b \u044d\u043a\u0440\u0430\u043d", @@ -314,8 +314,10 @@ "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", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderNotifications": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440", + "HeaderSelectPlayer": "\u041e\u0439\u043d\u0430\u0442\u049b\u044b\u0448\u0442\u044b \u0442\u0430\u04a3\u0434\u0430\u0443:", "ButtonSelect": "\u0411\u04e9\u043b\u0435\u043a\u0442\u0435\u0443", - "ButtonNew": "\u0416\u0430\u0441\u0430\u0443" + "ButtonNew": "\u0416\u0430\u0441\u0430\u0443", + "MessageInternetExplorerWebm": "Internet Explorer \u0430\u0440\u049b\u044b\u043b\u044b \u0435\u04a3 \u0436\u0430\u049b\u0441\u044b \u043d\u04d9\u0442\u0438\u0436\u0435\u043b\u0435\u0440 \u04af\u0448\u0456\u043d IE \u0448\u043e\u043b\u0493\u044b\u0448\u044b\u043d\u0430 \u0430\u0440\u043d\u0430\u043b\u0493\u0430\u043d WebM \u043f\u043b\u0430\u0433\u0438\u043d\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.", + "HeaderVideoError": "\u0411\u0435\u0439\u043d\u0435 \u049b\u0430\u0442\u0435\u0441\u0456" } \ 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 3ed0c8735..9c7d90092 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 c1700fe51..b16e5e101 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 10275847d..191a89000 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -314,8 +314,10 @@ "ButtonSubtitles": "Ondertitels", "ButtonScenes": "Scenes", "ButtonQuality": "Kwaliteit", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderNotifications": "Meldingen", + "HeaderSelectPlayer": "Selecteer Speler:", "ButtonSelect": "Selecteer", - "ButtonNew": "Nieuw" + "ButtonNew": "Nieuw", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Fout" } \ 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 3375c1a1d..44ac4c93f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", - "ButtonNew": "New" + "ButtonNew": "New", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 aac6a2c05..51559053d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -314,8 +314,10 @@ "ButtonSubtitles": "Legendas", "ButtonScenes": "Cenas", "ButtonQuality": "Qualidade", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderNotifications": "Avisos", + "HeaderSelectPlayer": "Selecione onde executar:", "ButtonSelect": "Selecionar", - "ButtonNew": "Nova" + "ButtonNew": "Nova", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 eb3740b84..473d75cb5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Selecionar", - "ButtonNew": "Novo" + "ButtonNew": "Novo", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 ea32fb932..2fa7f2ea7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -1,5 +1,5 @@ { - "SettingsSaved": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0431\u044b\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b.", + "SettingsSaved": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b.", "AddUser": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "Users": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", "Delete": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c", @@ -289,7 +289,7 @@ "ButtonRestart": "\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c", "MessagePleaseRefreshPage": "\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", "ButtonHide": "\u0421\u043a\u0440\u044b\u0442\u044c", - "MessageSettingsSaved": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u044b\u043b\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0430.", + "MessageSettingsSaved": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0431\u044b\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b.", "ButtonSignOut": "\u0412\u044b\u0439\u0442\u0438", "ButtonMyProfile": "\u041c\u043e\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c", "ButtonMyPreferences": "\u041c\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", @@ -302,11 +302,11 @@ "TabUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", "TabLibrary": "\u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0430", "TabMetadata": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435", - "TabDLNA": "DLNA-\u0440\u0435\u0436\u0438\u043c", + "TabDLNA": "DLNA \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f", "TabLiveTV": "\u0422\u0412 \u044d\u0444\u0438\u0440", "TabAutoOrganize": "\u0410\u0432\u0442\u043e\u0440\u0435\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f", "TabPlugins": "\u041f\u043b\u0430\u0433\u0438\u043d\u044b", - "TabAdvanced": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435", + "TabAdvanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e", "TabHelp": "\u0421\u043f\u0440\u0430\u0432\u043a\u0430", "TabScheduledTasks": "\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a", "ButtonFullscreen": "\u041f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d", @@ -314,8 +314,10 @@ "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b", "ButtonScenes": "\u0421\u0446\u0435\u043d\u044b", "ButtonQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e", - "HeaderNotifications": "Notifications", - "HeaderSelectPlayer": "Select Player:", + "HeaderNotifications": "\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f", + "HeaderSelectPlayer": "\u0412\u044b\u0431\u043e\u0440 \u043f\u043b\u0435\u0439\u0435\u0440\u0430:", "ButtonSelect": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c", - "ButtonNew": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c" + "ButtonNew": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c", + "MessageInternetExplorerWebm": "\u0414\u043b\u044f \u043d\u0430\u0438\u043b\u0443\u0447\u0448\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c Internet Explorer, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d WebM \u0434\u043b\u044f IE.", + "HeaderVideoError": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0438\u0434\u0435\u043e" } \ 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 421a0dea9..a8ae113be 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "V\u00e4lj", - "ButtonNew": "Nytillkommet" + "ButtonNew": "Nytillkommet", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 0f93d0695..3440a8ab0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "L\u1ef1a ch\u1ecdn", - "ButtonNew": "M\u1edbi" + "ButtonNew": "M\u1edbi", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 fe7d37994..763337f43 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -317,5 +317,7 @@ "HeaderNotifications": "Notifications", "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "\u9078\u64c7", - "ButtonNew": "\u5275\u5efa" + "ButtonNew": "\u5275\u5efa", + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error" } \ 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 df4178c6a..81922f713 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 461e56f49..a56ccf4de 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 d6cdfe028..729768b78 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -95,7 +95,7 @@ "TabSuggested": "Doporu\u010den\u00e9", "TabLatest": "Posledn\u00ed", "TabUpcoming": "Nadch\u00e1zej\u00edc\u00ed", - "TabShows": "Shows", + "TabShows": "Seri\u00e1ly", "TabEpisodes": "Epizody", "TabGenres": "\u017d\u00e1nry", "TabPeople": "Lid\u00e9", @@ -296,7 +296,7 @@ "PasswordLocalhostMessage": "Heslo nen\u00ed nutn\u00e9, pokud se p\u0159ihla\u0161ujete z m\u00edstn\u00edho PC-", "TabGuide": "Pr\u016fvodce", "TabChannels": "Kan\u00e1ly", - "TabCollections": "Collections", + "TabCollections": "Kolekce", "HeaderChannels": "Kan\u00e1ly", "TabRecordings": "Nahran\u00e9", "TabScheduled": "Napl\u00e1nov\u00e1no", @@ -643,7 +643,7 @@ "ButtonMute": "Mute", "HeaderLatestMedia": "Latest Media", "OptionSpecialFeatures": "Special Features", - "HeaderCollections": "Collections", + "HeaderCollections": "Kolekce", "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", @@ -780,8 +780,8 @@ "HeaderLatestChannelItems": "Latest Channel Items", "OptionNone": "None", "HeaderLiveTv": "Live TV", - "HeaderReports": "Reports", - "HeaderMetadataManager": "Metadata Manager", + "HeaderReports": "Hl\u00e1\u0161en\u00ed", + "HeaderMetadataManager": "Spr\u00e1vce metadat", "HeaderPreferences": "Preferences", "MessageLoadingChannels": "Loading channel content...", "ButtonMarkRead": "Ozna\u010dit jako p\u0159e\u010dten\u00e9", @@ -822,7 +822,7 @@ "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", - "TabXbmcMetadata": "Xbmc", + "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.", @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 8db9c68f8..c2fafba45 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 90c4c9e52..5ffff96dd 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 b778dc415..fe7451fed 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 653811157..5044e6d95 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 f315798ec..dc4ac80cb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 18e44a03c..9c43a9c9e 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 55439632f..98a36f8dd 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -884,5 +884,9 @@ "LabelView": "Vista:", "TabUsers": "Usuarios", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Tareas Programadas", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 fc02a9e8c..f5f3d9387 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -16,7 +16,7 @@ "LabelYoureDone": "Vous avez Termin\u00e9!", "WelcomeToMediaBrowser": "Bienvenue dans Media Browser!", "TitleMediaBrowser": "Media Browser", - "ThisWizardWillGuideYou": "Cet assistant vous guidera dans le processus de configuration. Pour commencer, SVP s\u00e9lectionnez votre langue pr\u00e9f\u00e9r\u00e9e.", + "ThisWizardWillGuideYou": "Cet assistant vous guidera dans le processus de configuration. Pour commencer, merci de s\u00e9lectionner votre langue pr\u00e9f\u00e9r\u00e9e.", "TellUsAboutYourself": "Parlez-nous de vous", "LabelYourFirstName": "Votre pr\u00e9nom:", "MoreUsersCanBeAddedLater": "D'autres utilisateurs pourront \u00eatre ajout\u00e9s ult\u00e9rieurement \u00e0 partir du tableau de bord.", @@ -151,7 +151,7 @@ "OptionVideoBitrate": "D\u00e9bit vid\u00e9o", "OptionResumable": "Reprenable", "ScheduledTasksHelp": "S\u00e9lectionnez une t\u00e2che pour ajuster sa programmation.", - "ScheduledTasksTitle": "T\u00e2ches programm\u00e9es", + "ScheduledTasksTitle": "T\u00e2ches planifi\u00e9es", "TabMyPlugins": "Mes Plugins", "TabCatalog": "Catalogue", "PluginsTitle": "Plugins", @@ -199,7 +199,7 @@ "OptionFriday": "Vendredi", "OptionSaturday": "Samedi", "HeaderManagement": "Gestion:", - "LabelManagement": "Management:", + "LabelManagement": "Gestion :", "OptionMissingImdbId": "ID IMDb manquant:", "OptionMissingTvdbId": "ID TVDB manquant:", "OptionMissingOverview": "R\u00e9sum\u00e9 manquant", @@ -239,7 +239,7 @@ "PismoMessage": "En utilisation de \"Pismo File Mount\" par une license fournie.", "TangibleSoftwareMessage": "Utilisation de convertisseurs Tangible Solutions Java\/C# par licence fournie.", "HeaderCredits": "Cr\u00e9dits", - "PleaseSupportOtherProduces": "SVP, soutenez les autres produits gratuits que nous utilisons:", + "PleaseSupportOtherProduces": "Merci de soutenir les autres produits gratuits que nous utilisons :", "VersionNumber": "Version {0}", "TabPaths": "Chemins d'acc\u00e8s", "TabServer": "Serveur", @@ -289,7 +289,7 @@ "OptionImageSavingStandard": "Standard - MB2", "ButtonSignIn": "Se connecter", "TitleSignIn": "Se connecter", - "HeaderPleaseSignIn": "SVP se connecter", + "HeaderPleaseSignIn": "Merci de vous identifier", "LabelUser": "Utilisateur:", "LabelPassword": "Mot de passe:", "ButtonManualLogin": "Connexion manuelle:", @@ -299,7 +299,7 @@ "TabCollections": "Collections", "HeaderChannels": "Cha\u00eenes", "TabRecordings": "Enregistrements", - "TabScheduled": "Programm\u00e9s", + "TabScheduled": "Planifi\u00e9s", "TabSeries": "S\u00e9ries", "TabFavorites": "Favoris", "TabMyLibrary": "Ma Biblioth\u00e8que", @@ -336,7 +336,7 @@ "LabelActiveServiceHelp": "Plusieurs Plugins de TV peuvent \u00eatre install\u00e9s mais seulement un \u00e0 la fois peut \u00eatre actif.", "OptionAutomatic": "Auto", "LiveTvPluginRequired": "Un fournisseur de service de TV en direct est requis pour continuer.", - "LiveTvPluginRequiredHelp": "SVP installer un de nos Plugins disponibles, comme Next Pvr ou ServerWmc.", + "LiveTvPluginRequiredHelp": "Merci d'installer un de nos plugins disponibles, comme Next Pvr ou ServerWmc.", "LabelCustomizeOptionsPerMediaType": "Personnaliser pour le type de m\u00e9dia:", "OptionDownloadThumbImage": "Vignette", "OptionDownloadMenuImage": "Menu", @@ -492,13 +492,13 @@ "LabelSupportAmount": "Montant (USD)", "HeaderSupportTheTeamHelp": "Aidez la continuation de ce projet en effectuant un don. Une portion de ce don contribuera au d\u00e9veloppement d'autres produits gratuits sur lesquels nous d\u00e9pendons.", "ButtonEnterSupporterKey": "Entrer la cl\u00e9 de supporteur", - "DonationNextStep": "Une fois compl\u00e9t\u00e9, SVP revenir et entrer la cl\u00e9 de supporteur re\u00e7ue par courriel.", + "DonationNextStep": "Une fois compl\u00e9t\u00e9, merci de revenir et saisir la cl\u00e9 de supporteur re\u00e7ue par courriel.", "AutoOrganizeHelp": "L'organisation automatique surveille vos r\u00e9pertoires de t\u00e9l\u00e9chargement et les nouveaux fichiers puis les d\u00e9place dans vos r\u00e9pertoires de m\u00e9dias. ", "AutoOrganizeTvHelp": "L'organisation de fichiers TV va seulement ajouter des \u00e9pisodes \u00e0 des s\u00e9ries existantes. Ce processus de cr\u00e9\u00e9ra pas de nouveaux r\u00e9pertoires de s\u00e9rie.", "OptionEnableEpisodeOrganization": "Activer l'organisation des nouvelles \u00e9pisodes", "LabelWatchFolder": "R\u00e9pertoire surveill\u00e9:", "LabelWatchFolderHelp": "Le serveur va utiliser ce r\u00e9pertoire pendant la t\u00e2che \"Organiser les nouveaux fichiers de m\u00e9dias\".", - "ButtonViewScheduledTasks": "Voir les t\u00e2ches programm\u00e9es", + "ButtonViewScheduledTasks": "Voir les t\u00e2ches planifi\u00e9es", "LabelMinFileSizeForOrganize": "Grosseur de fichier minimum (MB):", "LabelMinFileSizeForOrganizeHelp": "Les fichiers avec une grosseur inf\u00e9rieure seront ignor\u00e9s.", "LabelSeasonFolderPattern": "Mod\u00e8le de r\u00e9pertoire de saison:", @@ -529,12 +529,12 @@ "ButtonRestart": "Red\u00e9marrer", "ButtonShutdown": "\u00c9teindre", "ButtonUpdateNow": "Mettre \u00e0 jour maintenant", - "PleaseUpdateManually": "SVP fermer le serveur et mettre \u00e0 jour manuellement.", + "PleaseUpdateManually": "Merci d'\u00e9teindre le serveur et de le mettre \u00e0 jour manuellement.", "NewServerVersionAvailable": "Une nouvelle version de Media Browser Server est disponible!", "ServerUpToDate": "Media Browser est \u00e0 jour", "ErrorConnectingToMediaBrowserRepository": "Une erreur est survenue avec la connexion au r\u00e9f\u00e9rentiel de donn\u00e9es de Media Browser.", "LabelComponentsUpdated": "Les composants suivants ont \u00e9t\u00e9 install\u00e9s ou mis \u00e0 jour.", - "MessagePleaseRestartServerToFinishUpdating": "SVP red\u00e9marrer le serveur pour appliquer les mises \u00e0 jour.", + "MessagePleaseRestartServerToFinishUpdating": "Merci de red\u00e9marrer le serveur pour appliquer les mises \u00e0 jour.", "LabelDownMixAudioScale": "Boost audio lors de downmix:", "LabelDownMixAudioScaleHelp": "Boost audio lors de downmix. Mettre \u00e0 1 pour pr\u00e9server la valeur originale du volume.", "ButtonLinkKeys": "Lier les cl\u00e9s", @@ -586,7 +586,7 @@ "NotificationOptionVideoPlaybackStopped": "Lecture vid\u00e9o arr\u00eat\u00e9e", "NotificationOptionAudioPlaybackStopped": "Lecture audio arr\u00eat\u00e9e", "NotificationOptionGamePlaybackStopped": "Lecture de jeu arr\u00eat\u00e9e", - "NotificationOptionTaskFailed": "\u00c9chec de t\u00e2che programm\u00e9e", + "NotificationOptionTaskFailed": "\u00c9chec de t\u00e2che planifi\u00e9e", "NotificationOptionInstallationFailed": "\u00c9chec d'installation", "NotificationOptionNewLibraryContent": "Nouveau contenu ajout\u00e9", "NotificationOptionNewLibraryContentMultiple": "Nouveau contenu ajout\u00e9 (multiple)", @@ -627,10 +627,10 @@ "TabNowPlaying": "En cours de lecture", "TabNavigation": "Navigation", "TabControls": "Contr\u00f4les", - "ButtonFullscreen": "Toggle fullscreen", + "ButtonFullscreen": "Basculer en plein \u00e9cran", "ButtonScenes": "Sc\u00e8nes", "ButtonSubtitles": "Sous-titres", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "Pistes audio", "ButtonPreviousTrack": "Previous track", "ButtonNextTrack": "Next track", "ButtonStop": "Arr\u00eat", @@ -723,7 +723,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Ceci est requis pour certains appareils dont le time seek n'est pas tr\u00e8s bon", "HeaderSubtitleDownloadingHelp": "Lorsque Media Browser balaye vos fichiers vid\u00e9os, le serveur peut rechercher des sous-titres manquants et les t\u00e9l\u00e9charger en utilisant un fournisseur de sous-titre comme OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "T\u00e9l\u00e9charger les sous-titres pour:", - "MessageNoChapterProviders": "Install a chapter provider plugin such as ChapterDb to enable additional chapter options.", + "MessageNoChapterProviders": "Installer un plugin de fournisseur de chapitre tel que ChapterDb pour activer les options suppl\u00e9mentaires de chapitre.", "LabelSkipIfGraphicalSubsPresent": "Sauter la vid\u00e9o si elle contient d\u00e9j\u00e0 des sous-titres graphiques", "LabelSkipIfGraphicalSubsPresentHelp": "Garder des versions textes des sous-titres va \u00eatre plus efficace avec les appareils mobiles.", "TabSubtitles": "Sous-titres", @@ -731,7 +731,7 @@ "HeaderDownloadChaptersFor": "T\u00e9l\u00e9charger les noms de chapitre pour:", "LabelOpenSubtitlesUsername": "Nom d'utilisateur de Open Subtitles:", "LabelOpenSubtitlesPassword": "Mot de passe de Open Subtitles:", - "HeaderChapterDownloadingHelp": "When Media Browser scans your video files it can download friendly chapter names from the internet using chapter plugins such as ChapterDb.", + "HeaderChapterDownloadingHelp": "Lorsque Media Browser scanne vos fichiers vid\u00e9o, il peut facilement t\u00e9l\u00e9charger les noms de chapitre depuis Internet en utilisant le plugin de chapitre tel que ChapterDb.", "LabelPlayDefaultAudioTrack": "Utiliser la flux audio par d\u00e9faut peu importe la langue", "LabelSubtitlePlaybackMode": "Mode de sous-titres:", "LabelDownloadLanguages": "T\u00e9l\u00e9chargement de langues:", @@ -771,7 +771,7 @@ "LabelHomePageSection2": "Section 2 de la page de d\u00e9marrage:", "LabelHomePageSection3": "Section 3 de la page de d\u00e9marrage:", "LabelHomePageSection4": "Section 4 de la page de d\u00e9marrage:", - "OptionMyViewsButtons": "My views (buttons)", + "OptionMyViewsButtons": "Mes vues (bouttons)", "OptionMyViews": "Mes vues", "OptionMyViewsSmall": "Mes vues (petit)", "OptionResumablemedia": "Reprendre", @@ -794,13 +794,13 @@ "ButtonDismiss": "Annuler", "MessageLearnHowToCustomize": "Apprenez comment personnaliser cette page selon vos propres go\u00fbts. S\u00e9lectionnez votre ic\u00f4ne d'utilisateur dans le coin en haut, \u00e0 droite de l'\u00e9cran pour visionner et mettre \u00e0 jour vos pr\u00e9f\u00e9rences. ", "ButtonEditOtherUserPreferences": "Modifier les pr\u00e9f\u00e9rences personnelles de cet utilisateur.", - "LabelChannelStreamQuality": "Preferred internet stream quality:", - "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.", + "LabelChannelStreamQuality": "Qualit\u00e9 de diffusion internet pr\u00e9f\u00e9r\u00e9e :", + "LabelChannelStreamQualityHelp": "Avec une bande passante faible, limiter la qualit\u00e9 garantit un confort d'utilisation du streaming.", "OptionBestAvailableStreamQuality": "Meilleur disponible", "LabelEnableChannelContentDownloadingFor": "Activer le t\u00e9l\u00e9chargement de contenu de cha\u00eene pour:", - "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.", + "LabelEnableChannelContentDownloadingForHelp": "Certaines cha\u00eenes supportent la priorit\u00e9 de t\u00e9l\u00e9chargement de contenu lors du visionnage. Activez ceci pour les environnements \u00e0 bande passante faible afin de t\u00e9l\u00e9charger le contenu des cha\u00eenes pendant les horaires d'inactivit\u00e9. Le contenu est t\u00e9l\u00e9charg\u00e9 suivant la programmation de celui-ci dans les t\u00e2ches planifi\u00e9es.", "LabelChannelDownloadPath": "Chemin d'acc\u00e8s de t\u00e9l\u00e9chargement de contenu de cha\u00eene:", - "LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.", + "LabelChannelDownloadPathHelp": "Sp\u00e9cifiez un chemin de t\u00e9l\u00e9chargements personnalis\u00e9 si besoin. Laissez vide pour t\u00e9l\u00e9charger dans un r\u00e9pertoire interne du programme.", "LabelChannelDownloadAge": "Supprimer le contenu apr\u00e8s: (jours)", "LabelChannelDownloadAgeHelp": "Le contenu t\u00e9l\u00e9charg\u00e9 plus vieux sera supprim\u00e9. Par contre, il sera toujours disponible par flux Internet (en ligne).", "ChannelSettingsFormHelp": "Installer des cha\u00eenes comme \"Trailers\" and \"Vimeo\" par le catalogue de Plugins.", @@ -811,78 +811,82 @@ "ViewTypeMusic": "Musique", "ViewTypeBoxSets": "Collections", "ViewTypeChannels": "Cha\u00eenes", - "ViewTypeLiveTV": "TV en directe", + "ViewTypeLiveTV": "TV en direct", "HeaderOtherDisplaySettings": "Param\u00e8tres d'affichage", "HeaderMyViews": "Mes affichages", - "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.", + "LabelSelectFolderGroups": "Grouper automatiquement le contenu des r\u00e9pertoires suivants dans les vues tels que Films, Musiques et TV :", + "LabelSelectFolderGroupsHelp": "Les r\u00e9pertoires qui ne sont pas coch\u00e9s, seront affich\u00e9s tels quels avec leur propre disposition.", "OptionDisplayAdultContent": "Afficher le contenu adulte", "OptionLibraryFolders": "R\u00e9pertoires de m\u00e9dias", "TitleRemoteControl": "Acc\u00e8s \u00e0 distance", "OptionLatestTvRecordings": "Les plus r\u00e9cents enregistrements", "LabelProtocolInfo": "Infos sur le protocol:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "LabelProtocolInfoHelp": "La valeur sera utilis\u00e9e par le p\u00e9riph\u00e9rique pour r\u00e9pondre aux requ\u00eates GetProtocolInfo.", "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.", + "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": "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.", + "LabelXbmcMetadataUserHelp": "Activer ceci pour synchroniser en permanence 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.", + "LabelXbmcMetadataSaveImagePathsHelp": "Cela est r\u00e9command\u00e9 si les noms des fichiers d'images ne sont pas conformes aux directives d'Xbmc.", + "LabelXbmcMetadataEnablePathSubstitution": "Activer le remplacement de chemin", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Active la substitution de chemin des chemins d'image en utilisant les param\u00e8tres de substitution de chemin du serveur.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Voir la substitution de chemin.", + "LabelGroupChannelsIntoViews": "Afficher directement les cha\u00eenes suivantes dans mes vues.", + "LabelGroupChannelsIntoViewsHelp": "Si activ\u00e9, ces cha\u00eenes seront directement affich\u00e9es \u00e0 c\u00f4t\u00e9 des autres vues. Si d\u00e9sactiv\u00e9, elles seront affich\u00e9es dans une vue de cha\u00eenes s\u00e9par\u00e9es.", + "LabelDisplayCollectionsView": "Afficher un aper\u00e7u de collections pour montrer les collections de film", + "LabelXbmcMetadataEnableExtraThumbs": "Copier l'extrafanart dans les extrathumbs", + "LabelXbmcMetadataEnableExtraThumbsHelp": "Lors du t\u00e9l\u00e9chargement d'images, elles peuvent \u00eatre sauvegard\u00e9es en tant que extrafanart ou extrathumbs pour une compatibilit\u00e9 maximum avec le skin Xbmc.", "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", + "HeaderServerLogFiles": "Fichiers log du serveur :", + "TabBranding": "Slogan", + "HeaderBrandingHelp": "Personnaliser l'apparence de Media Browser pour r\u00e9pondre aux besoins de votre groupe ou organisation.", + "LabelLoginDisclaimer": "Avertissement sur la page d'accueil :", + "LabelLoginDisclaimerHelp": "Ce sera affich\u00e9 en bas de la page de connexion.", + "LabelAutomaticallyDonate": "Donner ce montant automatiquement tout les 6 mois.", + "LabelAutomaticallyDonateHelp": "Vous pouvez annuler via votre compte Paypal n'importe quand.", + "OptionList": "Liste", + "TabDashboard": "Tableau de bord", + "TitleServer": "Serveur", + "LabelCache": "Cache :", + "LabelLogs": "Logs :", + "LabelMetadata": "M\u00e9tadonn\u00e9es :", + "LabelImagesByName": "Images tri\u00e9es par nom :", + "LabelTranscodingTemporaryFiles": "Transcodage de fichiers temporaires :", + "HeaderLatestMusic": "Derni\u00e8re musique", + "HeaderBranding": "Slogan", + "HeaderApiKeys": "Cl\u00e9s API", + "HeaderApiKeysHelp": "Les applications externes n\u00e9cessitent d'avoir une cl\u00e9 API pour communiquer avec Media Browser. Les cl\u00e9s sont d\u00e9livr\u00e9es en se connectant avec un compte Media Browser, ou en octroyant manuellement la cl\u00e9 depuis l'application.", + "HeaderApiKey": "Cl\u00e9 API", "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.", - "HeaderHttpHeaders": "Http Headers", - "HeaderIdentificationHeader": "Identification Header", - "LabelValue": "Value:", - "LabelMatchType": "Match type:", - "OptionEquals": "Equals", + "HeaderDevice": "P\u00e9riph\u00e9rique", + "HeaderUser": "Utilisateur", + "HeaderDateIssued": "Date de publication", + "LabelChapterName": "Chapitre {0}", + "HeaderNewApiKey": "Nouvelle cl\u00e9 API", + "LabelAppName": "Nom de l'app", + "LabelAppNameExample": "Exemple: Sickbeard, NzbDrone", + "HeaderNewApiKeyHelp": "Allouer \u00e0 une application des droits pour communiquer avec Media Browser.", + "HeaderHttpHeaders": "Ent\u00eates HTTP", + "HeaderIdentificationHeader": "Ent\u00eate d'identification", + "LabelValue": "Valeur :", + "LabelMatchType": "Type recherch\u00e9 :", + "OptionEquals": "Equivalents", "OptionRegex": "Regex", - "OptionSubstring": "Substring", - "TabView": "View", - "TabSort": "Sort", - "TabFilter": "Filter", - "ButtonView": "View", - "LabelPageSize": "Item limit:", - "LabelView": "View:", - "TabUsers": "Users", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "OptionSubstring": "Sous-cha\u00eene", + "TabView": "Voir", + "TabSort": "Trier", + "TabFilter": "Filtrer", + "ButtonView": "Voir", + "LabelPageSize": "Limite de l'item :", + "LabelView": "Voir :", + "TabUsers": "Utilisateurs", + "HeaderFeatures": "Fonctionnalit\u00e9s", + "HeaderAdvanced": "Avanc\u00e9", + "ButtonSync": "Sync", + "TabScheduledTasks": "T\u00e2ches planifi\u00e9es", + "HeaderChapters": "Chapitres", + "HeaderResumeSettings": "Reprendre les param\u00e8tres" } \ 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 effeb0ad6..43135ec81 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 2757333f1..e76d47a04 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -198,7 +198,7 @@ "OptionThursday": "Giovedi", "OptionFriday": "Venerdi", "OptionSaturday": "Sabato", - "HeaderManagement": "Gestione:", + "HeaderManagement": "Gestione :", "LabelManagement": "Management:", "OptionMissingImdbId": "IMDB id mancante", "OptionMissingTvdbId": "TheTVDB Id mancante", @@ -257,13 +257,13 @@ "ButtonSelectDirectory": "Seleziona cartella", "LabelCustomPaths": "Specifica un percorso personalizzato.Lasciare vuoto per usare quello predefinito", "LabelCachePath": "Percorso Cache:", - "LabelCachePathHelp": "Questa cartella contiene la cache come files e immagini.", + "LabelCachePathHelp": "Questa cartella contiene la cache come files e immagini .", "LabelImagesByNamePath": "Percorso immagini per nome:", - "LabelImagesByNamePathHelp": "Questa cartella contiene le immagini degli attori,generi, e studio", + "LabelImagesByNamePathHelp": "Specificare un percorso personalizzato per le immagini scaricato attore, artista, genere e in studio.", "LabelMetadataPath": "Percorso dei file METADATI:", - "LabelMetadataPathHelp": "Questa cartella contiene i files relativi ai metadati e immagini che non sono stati salvati nella cartella dei Media.", + "LabelMetadataPathHelp": "Specificare un percorso personalizzato per opere d'arte scaricato e metadati, se non il salvataggio in cartelle multimediali.", "LabelTranscodingTempPath": "Cartella temporanea per la trascodifica:", - "LabelTranscodingTempPathHelp": "Questa cartella contiene i file usati dalla trascodifica.", + "LabelTranscodingTempPathHelp": "Questa cartella contiene i file di lavoro utilizzati dal transcoder. Specificare un percorso personalizzato, oppure lasciare vuoto per utilizzare l'impostazione predefinita all'interno della cartella dei dati del server.", "TabBasics": "Base", "TabTV": "SerieTv", "TabGames": "Giochi", @@ -280,13 +280,13 @@ "LabelAutomaticUpdatesFanartHelp": "Se abilitato le nuove immagini verranno scaricate automaticamente da fanart.tv.Le immagini esistenti non verranno sovrascritte.", "LabelAutomaticUpdatesTmdbHelp": "Se abilitato le nuove immagini verranno scaricate automaticamente da ThemovieDb.org.Le immagini esistenti non verranno sovrascritte.", "LabelAutomaticUpdatesTvdbHelp": "Se abilitato le nuove immagini verranno scaricate automaticamente da TheTvDB.com.Le immagini esistenti non verranno sovrascritte.", - "ExtractChapterImagesHelp": "Estrazione di immagini capitoli permetter\u00e0 ai clienti di visualizzare i menu di selezione delle scene grafiche. Il processo pu\u00f2 essere lento, e pu\u00f2 richiedere diversi gigabyte di spazio. Funziona come una operazione pianificata alle 04:00, anche se questo \u00e8 configurabile nella zona di operazioni pianificate. Non \u00e8 consigliabile eseguire questa operazione durante le ore diurne.", + "ExtractChapterImagesHelp": "Estrazione di immagini capitoli permetter\u00e0 ai clienti di visualizzare i menu di selezione delle scene grafiche. Il processo pu\u00f2 essere lento, cpu-intensive e pu\u00f2 richiedere diversi gigabyte di spazio. Funziona quando vengono scoperti i video, e anche come un compito serale in programma a 04:00. Il programma \u00e8 configurabile nella zona di operazioni pianificate. Non \u00e8 consigliabile eseguire questa operazione durante le ore di picco.", "LabelMetadataDownloadLanguage": "Lingua preferita per il download:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Convenzione per il salvataggio di immagini:", "LabelImageSavingConventionHelp": "Media Browser riconosce le immagini dalla maggior parte delle principali applicazioni multimediali. Scegliere la convenzione piu adatta a te.", - "OptionImageSavingCompatible": "Compatibile - MB3\/Plex\/Xbmc", - "OptionImageSavingStandard": "Standard - MB3\/MB2", + "OptionImageSavingCompatible": "Compatibile- Media Browser\/Xbmc\/Plex", + "OptionImageSavingStandard": "Standard - MB2", "ButtonSignIn": "Accedi", "TitleSignIn": "Accedi", "HeaderPleaseSignIn": "Per favore accedi", @@ -484,9 +484,9 @@ "LabelSkipped": "Saltato", "HeaderEpisodeOrganization": "Organizzazione Episodi", "LabelSeries": "Series:", - "LabelSeasonNumber": "Numero Stagione:", - "LabelEpisodeNumber": "Numero Episodio:", - "LabelEndingEpisodeNumber": "Ultimo Episodio Numero:", + "LabelSeasonNumber": "Numero Stagione", + "LabelEpisodeNumber": "Numero Episodio", + "LabelEndingEpisodeNumber": "Ultimo Episodio Numero", "LabelEndingEpisodeNumberHelp": "\u00e8 richiesto solo se ci sono pi\u00f9 file per espisodio", "HeaderSupportTheTeam": "Team di supporto di Media Browser", "LabelSupportAmount": "Ammontare (Dollari)", @@ -535,7 +535,7 @@ "ErrorConnectingToMediaBrowserRepository": "Si \u00e8 verificato un errore durante la connessione al repository Media Browser remoto.", "LabelComponentsUpdated": "I seguenti componenti sono stati installati o aggiornati:", "MessagePleaseRestartServerToFinishUpdating": "Si prega di riavviare il server per completare l'applicazione degli aggiornamenti.", - "LabelDownMixAudioScale": "Audio Boost Mix Scala:", + "LabelDownMixAudioScale": "Audio Boost Mix Scala :", "LabelDownMixAudioScaleHelp": "Audio Mix: Valore originale 1", "ButtonLinkKeys": "Lnks Chiavi:", "LabelOldSupporterKey": "Vecchie Chiavi dl donatore", @@ -550,8 +550,8 @@ "ButtonRetrieveKey": "Ottieni chiave", "LabelSupporterKey": "Chiave ( incollala dalla mail ricevuta)", "LabelSupporterKeyHelp": "Inserisci la chiave per avere nuovi benefit che la comunit\u00e0 di Media Browser ha sviluppato per te", - "MessageInvalidKey": "Chiave MB3 mancante o invalida", - "ErrorMessageInvalidKey": "Per avere qualsiasi contenuto premium devi essere registrato. Devi anche essere un donatore. Si prega di donae er sostenere lo sviluppo di MB3.Grazie.", + "MessageInvalidKey": "Chiave MB3 mancante o invalida.", + "ErrorMessageInvalidKey": "Per qualsiasi contenuto premium devi essere registrato. \u00e8 necessario anche essere un browser Supporter multimediale. Si prega di donare e sostenere il continuo sviluppo del prodotto di base. Grazie.", "HeaderDisplaySettings": "Configurazione Monitor", "TabPlayTo": "Riproduci su", "LabelEnableDlnaServer": "Abilita server Dlna", @@ -580,9 +580,9 @@ "NotificationOptionPluginUpdateInstalled": "Aggiornamento del plugin installato", "NotificationOptionPluginInstalled": "plugin installato", "NotificationOptionPluginUninstalled": "Plugin disinstallato", - "NotificationOptionVideoPlayback": "Riproduzione video", - "NotificationOptionAudioPlayback": "Riproduzione Audio", - "NotificationOptionGamePlayback": "Riproduzione gioco", + "NotificationOptionVideoPlayback": "La riproduzione video \u00e8 iniziato", + "NotificationOptionAudioPlayback": "Riproduzione audio iniziato", + "NotificationOptionGamePlayback": "La riproduzione di gioco \u00e8 parita", "NotificationOptionVideoPlaybackStopped": "Video Fermato", "NotificationOptionAudioPlaybackStopped": "Audio Fermato", "NotificationOptionGamePlaybackStopped": "Gioco Fermato", @@ -627,10 +627,10 @@ "TabNowPlaying": "In esecuzione", "TabNavigation": "Navigazione", "TabControls": "Controlli", - "ButtonFullscreen": "Toggle fullscreen", + "ButtonFullscreen": "Tutto Schermo", "ButtonScenes": "Scene", "ButtonSubtitles": "Sottotitoli", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "Tracce audio", "ButtonPreviousTrack": "Previous track", "ButtonNextTrack": "Next track", "ButtonStop": "Stop", @@ -723,7 +723,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Questo \u00e8 necessario per alcuni dispositivi che il tempo non cercano molto bene.", "HeaderSubtitleDownloadingHelp": "Quando Media Browser esegue la scansione dei file video \u00e8 possibile cercare i sottotitoli mancanti, e scaricarli utilizzando un provider sottotitolo come OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "Scarica sottotitoli per:", - "MessageNoChapterProviders": "Installare un plugin fornitore capitolo come ChapterDb o tagChimp per attivare le opzioni capitolo aggiuntivo.", + "MessageNoChapterProviders": "Installare un plugin fornitore capitolo come ChapterDb per attivare le opzioni capitolo aggiuntivo.", "LabelSkipIfGraphicalSubsPresent": "Salta se il video contiene gi\u00e0 i sottotitoli grafici", "LabelSkipIfGraphicalSubsPresentHelp": "Mantenere le versioni del testo di sottotitoli si tradurr\u00e0 in consegna pi\u00f9 efficiente ai clienti di telefonia mobile.", "TabSubtitles": "sottotitoli", @@ -731,7 +731,7 @@ "HeaderDownloadChaptersFor": "Scarica i nomi dei capitoli per:", "LabelOpenSubtitlesUsername": "Sottotitoli utente:", "LabelOpenSubtitlesPassword": "Sottotitoli password:", - "HeaderChapterDownloadingHelp": "Quando Media Browser esegue la scansione dei file video \u00e8 possibile scaricare i nomi dei capitoli amichevoli da internet utilizzando i plugin capitolo come ChapterDb e tagChimp.", + "HeaderChapterDownloadingHelp": "Quando Media Browser esegue la scansione dei file video \u00e8 possibile scaricare i nomi dei capitoli amichevoli da internet utilizzando i plugin capitolo come ChapterDb", "LabelPlayDefaultAudioTrack": "Riprodurre la traccia audio di default indipendentemente dalla lingua", "LabelSubtitlePlaybackMode": "Modalit\u00e0 Sottotitolo:", "LabelDownloadLanguages": "Scarica lingue:", @@ -817,7 +817,7 @@ "LabelSelectFolderGroups": "Automaticamente i contenuti del gruppo dalle seguenti cartelle nella vista come film, musica e TV:", "LabelSelectFolderGroupsHelp": "Le cartelle che siano deselezionate verranno visualizzati da soli nel loro punto di vista.", "OptionDisplayAdultContent": "Visualizzazioni contenuti per adulti", - "OptionLibraryFolders": "Vista", + "OptionLibraryFolders": "Cartelle dei media", "TitleRemoteControl": "Telecomando", "OptionLatestTvRecordings": "Ultime registrazioni", "LabelProtocolInfo": "Info.protocollo:", @@ -835,7 +835,7 @@ "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Vedere sostituzione di percorso.", "LabelGroupChannelsIntoViews": "Visualizzare i seguenti canali direttamente dentro le mie visite:", "LabelGroupChannelsIntoViewsHelp": "Se abilitata, questi canali verranno visualizzati direttamente accanto ad altri punti di vista. Se disattivato, saranno visualizzati all'interno di una visione canali separati.", - "LabelDisplayCollectionsView": "Visualizzare una vista Collezioni per mostrare collezioni di film", + "LabelDisplayCollectionsView": "Visualizzare una vista collezioni di mostrare collezioni di film", "LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs", "LabelXbmcMetadataEnableExtraThumbsHelp": "Quando si scaricano le immagini possono essere salvate in entrambi extrafanart e extrathumbs per la massima compatibilit\u00e0 cutanea XBMC.", "TabServices": "Servizi", @@ -845,7 +845,7 @@ "HeaderBrandingHelp": "Personalizzare l'aspetto del browser media per soddisfare le esigenze del vostro gruppo o organizzazione.", "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "Questo verr\u00e0 visualizzato nella parte inferiore della pagina di accesso.", - "LabelAutomaticallyDonate": "Donare automaticamente questo importo ogni mese", + "LabelAutomaticallyDonate": "Donare automaticamente questo importo ogni sei mesi", "LabelAutomaticallyDonateHelp": "\u00c8 possibile annullare in qualsiasi momento tramite il vostro conto PayPal.", "OptionList": "Lista", "TabDashboard": "Pannello Controllo", @@ -864,25 +864,29 @@ "HeaderDevice": "Dispositivo", "HeaderUser": "Utente", "HeaderDateIssued": "data di pubblicazione", - "LabelChapterName": "Chapter {0}", + "LabelChapterName": "Capitolo {0}", "HeaderNewApiKey": "Nessuna Chiave Api", "LabelAppName": "Nome app", "LabelAppNameExample": "Example: Sickbeard, NzbDrone", "HeaderNewApiKeyHelp": "Garantisci all'applicazione il permesso di comunicare con Media Browser.", "HeaderHttpHeaders": "Http Headers", "HeaderIdentificationHeader": "Identification Header", - "LabelValue": "Value:", + "LabelValue": "valore:", "LabelMatchType": "Match type:", - "OptionEquals": "Equals", + "OptionEquals": "Uguale", "OptionRegex": "Regex", "OptionSubstring": "Substring", - "TabView": "View", - "TabSort": "Sort", - "TabFilter": "Filter", - "ButtonView": "View", - "LabelPageSize": "Item limit:", - "LabelView": "View:", - "TabUsers": "Users", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "TabView": "Vista", + "TabSort": "Ordina", + "TabFilter": "Filtra", + "ButtonView": "Vista", + "LabelPageSize": "Limite articolo:", + "LabelView": "Vista:", + "TabUsers": "Utenti", + "HeaderFeatures": "Caratteristiche", + "HeaderAdvanced": "Avanzato", + "ButtonSync": "Sync", + "TabScheduledTasks": "Operazioni pianificate", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 b588e6c11..9a1847ea4 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -55,7 +55,7 @@ "TabProfile": "\u041f\u0440\u043e\u0444\u0438\u043b\u044c", "TabMetadata": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a", "TabImages": "\u0421\u0443\u0440\u0435\u0442\u0442\u0435\u0440", - "TabNotifications": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443", + "TabNotifications": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440", "TabCollectionTitles": "\u0410\u0442\u0430\u0443\u043b\u0430\u0440", "LabelDisplayMissingEpisodesWithinSeasons": "\u0416\u043e\u049b \u044d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440\u0434\u044b \u043c\u0430\u0443\u0441\u044b\u043c \u0456\u0448\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443", "LabelUnairedMissingEpisodesWithinSeasons": "\u041a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u043c\u0435\u0433\u0435\u043d \u044d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440\u0434\u044b \u043c\u0430\u0443\u0441\u044b\u043c \u0456\u0448\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443", @@ -167,7 +167,7 @@ "OptionDvd": "DVD", "OptionIso": "ISO", "Option3D": "3D", - "LabelFeatures": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440:", + "LabelFeatures": "\u041c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440:", "LabelService": "\u049a\u044b\u0437\u043c\u0435\u0442:", "LabelStatus": "\u041a\u04af\u0439:", "LabelVersion": "\u041d\u04b1\u0441\u049b\u0430:", @@ -181,16 +181,16 @@ "TabTrailers": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440", "HeaderLatestMovies": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456 \u0444\u0438\u043b\u044c\u043c\u0434\u0435\u0440", "HeaderLatestTrailers": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440", - "OptionHasSpecialFeatures": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440", + "OptionHasSpecialFeatures": "\u041c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440", "OptionImdbRating": "IMDb \u0431\u0430\u0493\u0430\u043b\u0430\u0443\u044b", "OptionParentalRating": "\u0416\u0430\u0441\u0442\u0430\u0441 \u0441\u0430\u043d\u0430\u0442", "OptionPremiereDate": "\u0422\u04b1\u0441\u0430\u0443\u043a\u0435\u0441\u0435\u0440 \u043a\u04af\u043d-\u0430\u0439\u044b", "TabBasic": "\u041d\u0435\u0433\u0456\u0437\u0433\u0456\u043b\u0435\u0440", - "TabAdvanced": "\u041a\u0435\u04a3\u0435\u0439\u0442\u0456\u043b\u0433\u0435\u043d\u0434\u0435\u0440", + "TabAdvanced": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430", "HeaderStatus": "\u041a\u04af\u0439", "OptionContinuing": "\u0416\u0430\u043b\u0493\u0430\u0441\u0443\u0434\u0430", "OptionEnded": "\u0410\u044f\u049b\u0442\u0430\u043b\u0434\u044b", - "HeaderAirDays": "\u042d\u0444\u0438\u0440 \u043a\u04af\u043d\u0434\u0435\u0440\u0456:", + "HeaderAirDays": "\u042d\u0444\u0438\u0440 \u043a\u04af\u043d\u0434\u0435\u0440\u0456", "OptionSunday": "\u0436\u0435\u043a\u0441\u0435\u043d\u0431\u0456", "OptionMonday": "\u0434\u04af\u0439\u0441\u0435\u043d\u0431\u0456", "OptionTuesday": "\u0441\u0435\u0439\u0441\u0435\u043d\u0431\u0456", @@ -198,8 +198,8 @@ "OptionThursday": "\u0431\u0435\u0439\u0441\u0435\u043d\u0431\u0456", "OptionFriday": "\u0436\u04b1\u043c\u0430", "OptionSaturday": "\u0441\u0435\u043d\u0431\u0456", - "HeaderManagement": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a:", - "LabelManagement": "Management:", + "HeaderManagement": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440", + "LabelManagement": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440", "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", @@ -244,7 +244,7 @@ "TabPaths": "\u0416\u043e\u043b\u0434\u0430\u0440", "TabServer": "\u0421\u0435\u0440\u0432\u0435\u0440", "TabTranscoding": "\u049a\u0430\u0439\u0442\u0430 \u043a\u043e\u0434\u0442\u0430\u0443", - "TitleAdvanced": "\u041a\u0435\u04a3\u0435\u0439\u0442\u0456\u043b\u0433\u0435\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440", + "TitleAdvanced": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430", "LabelAutomaticUpdateLevel": "\u0410\u0432\u0442\u043e\u0436\u0430\u04a3\u0430\u0440\u0442\u0443 \u0434\u0435\u04a3\u0433\u0435\u0439\u0456", "OptionRelease": "\u0420\u0435\u0441\u043c\u0438 \u0448\u044b\u0493\u0430\u0440\u044b\u043b\u044b\u043c", "OptionBeta": "\u0411\u0435\u0442\u0430 \u043d\u04b1\u0441\u049b\u0430", @@ -255,15 +255,15 @@ "LabelRunServerAtStartup": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u044b\u043b\u0443\u0434\u0430\u043d \u0431\u0430\u0441\u0442\u0430\u043f \u043e\u0440\u044b\u043d\u0434\u0430\u0443", "LabelRunServerAtStartupHelp": "\u0411\u04b1\u043b Windows \u0436\u04b1\u043c\u044b\u0441\u044b\u043d \u0431\u0430\u0441\u0442\u0430\u0493\u0430\u043d\u0434\u0430 \u0436\u04af\u0439\u0435\u043b\u0456\u043a \u0442\u0430\u049b\u0442\u0430\u0434\u0430\u0493\u044b \u0431\u0435\u043b\u0433\u0456\u0448\u0435 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u044b\u043b\u0430\u0434\u044b. Windows \u049b\u044b\u0437\u043c\u0435\u0442\u0456\u043d \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443 \u04af\u0448\u0456\u043d, \u049b\u04b1\u0441\u0431\u0435\u043b\u0433\u0456\u043d\u0456 \u0430\u043b\u044b\u04a3\u044b\u0437 \u0436\u04d9\u043d\u0435 \u049b\u044b\u0437\u043c\u0435\u0442\u0442\u0456 Windows \u049a\u044b\u0437\u043c\u0435\u0442\u0442\u0435\u0440 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0456 \u0430\u0440\u049b\u044b\u043b\u044b \u043e\u0440\u044b\u043d\u0434\u0430\u04a3\u044b\u0437. \u041d\u0430\u0437\u0430\u0440 \u0430\u0443\u0434\u0430\u0440\u044b\u04a3\u044b\u0437! \u0411\u04b1\u043b \u0435\u043a\u0435\u0443\u0456\u043d \u0441\u043e\u043b \u043c\u0435\u0437\u0433\u0456\u043b\u0434\u0435 \u0431\u0456\u0440\u0433\u0435 \u043e\u0440\u044b\u043d\u0434\u0430\u0443 \u043c\u04af\u043c\u043a\u0456\u043d \u0435\u043c\u0435\u0441, \u0441\u043e\u043d\u044b\u043c\u0435\u043d \u049b\u044b\u0437\u043c\u0435\u0442\u0442\u0456 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443 \u0430\u043b\u0434\u044b\u043d\u0430\u043d \u0436\u04af\u0439\u0435\u043b\u0456\u043a \u0442\u0430\u049b\u0442\u0430\u0434\u0430\u0493\u044b \u0431\u0435\u043b\u0433\u0456\u0448\u0435\u0434\u0435\u043d \u0448\u044b\u0493\u044b\u04a3\u044b\u0437.", "ButtonSelectDirectory": "\u049a\u0430\u0442\u0430\u043b\u043e\u0433\u0442\u044b \u0431\u04e9\u043b\u0435\u043a\u0442\u0435\u0443", - "LabelCustomPaths": "\u049a\u0430\u043b\u0430\u0443 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u0430\u0440\u0434\u044b \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u04a3\u0456\u0437. \u04d8\u0434\u0435\u043f\u043a\u0456\u043b\u0435\u0440\u0434\u0456 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u04e9\u0440\u0456\u0441\u0442\u0435\u0440\u0434\u0456 \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", + "LabelCustomPaths": "\u049a\u0430\u043b\u0430\u0443\u044b\u04a3\u044b\u0437 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u0430\u0440\u0434\u044b \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437. \u04d8\u0434\u0435\u043f\u043a\u0456\u043b\u0435\u0440\u0434\u0456 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u04e9\u0440\u0456\u0441\u0442\u0435\u0440\u0434\u0456 \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", "LabelCachePath": "Cache \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u044b\u04a3 \u0436\u043e\u043b\u044b:", - "LabelCachePathHelp": "\u0411\u04b1\u043b \u049b\u0430\u043b\u0442\u0430 \u049b\u04b1\u0440\u0430\u043c\u044b\u043d\u0434\u0430 \u0441\u0443\u0440\u0435\u0442 \u0441\u0438\u044f\u049b\u0442\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u043a\u044d\u0448 \u0444\u0430\u0439\u043b\u0434\u0430\u0440\u044b \u0431\u0430\u0440.", + "LabelCachePathHelp": "\u0421\u0443\u0440\u0435\u0442 \u0441\u0438\u044f\u049b\u0442\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u043a\u044d\u0448 \u0444\u0430\u0439\u043b\u0434\u0430\u0440\u044b \u04af\u0448\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u0430\u0439\u0493\u0430\u0441\u044b\u043c\u0434\u044b \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437.", "LabelImagesByNamePath": "Images by name \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u044b\u04a3 \u0436\u043e\u043b\u044b:", - "LabelImagesByNamePathHelp": "\u0411\u04b1\u043b \u049b\u0430\u043b\u0442\u0430 \u049b\u04b1\u0440\u0430\u043c\u044b\u043d\u0434\u0430 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d \u0430\u043a\u0442\u0435\u0440, \u043e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b, \u0436\u0430\u043d\u0440, \u0436\u04d9\u043d\u0435 \u0441\u0442\u0443\u0434\u0438\u044f \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456 \u0431\u0430\u0440.", + "LabelImagesByNamePathHelp": "\u0416\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d \u0430\u043a\u0442\u0435\u0440, \u043e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b, \u0436\u0430\u043d\u0440, \u0436\u04d9\u043d\u0435 \u0441\u0442\u0443\u0434\u0438\u044f \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456 \u04af\u0448\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u0430\u0439\u0493\u0430\u0441\u044b\u043c\u0434\u044b \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437.", "LabelMetadataPath": "Metadata \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u044b\u04a3 \u0436\u043e\u043b\u044b:", - "LabelMetadataPathHelp": "\u0411\u04b1\u043b \u0436\u0430\u0439\u0493\u0430\u0441\u044b\u043c \u0456\u0448\u0456\u043d\u0434\u0435 \u0442\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b\u043d\u0434\u0430 \u0441\u0430\u049b\u0442\u0430\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0441\u044b \u0456\u0441\u0442\u0435\u043b\u043c\u0435\u0433\u0435\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u043c\u0435 \u043c\u0435\u043d \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a \u0431\u0430\u0440.", + "LabelMetadataPathHelp": "\u0416\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u043c\u0435\u043b\u0435\u0440 \u0431\u0435\u043d \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u04af\u0448\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u0430\u0439\u0493\u0430\u0441\u044b\u043c\u0434\u044b \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437.", "LabelTranscodingTempPath": "Transcoding temporary \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u044b\u04a3 \u0436\u043e\u043b\u044b:", - "LabelTranscodingTempPathHelp": "\u0411\u04b1\u043b \u049b\u0430\u043b\u0442\u0430 \u049b\u04b1\u0440\u0430\u043c\u044b\u043d\u0434\u0430 \u049b\u0430\u0439\u0442\u0430 \u043a\u043e\u0434\u0442\u0430\u0443 \u049b\u04b1\u0440\u0430\u043b\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0430\u0442\u044b\u043d \u0436\u04b1\u043c\u044b\u0441 \u0444\u0430\u0439\u043b\u0434\u0430\u0440\u044b \u0431\u0430\u0440. \u0422\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u044b \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u04a3\u0456\u0437, \u043d\u0435\u043c\u0435\u0441\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u049b\u0430\u043b\u0442\u0430\u0441\u044b \u0456\u0448\u0456\u043d\u0434\u0435\u0433\u0456 \u04d9\u0434\u0435\u043f\u043a\u0456\u0441\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", + "LabelTranscodingTempPathHelp": "\u0411\u04b1\u043b \u049b\u0430\u043b\u0442\u0430 \u049b\u04b1\u0440\u0430\u043c\u044b\u043d\u0434\u0430 \u049b\u0430\u0439\u0442\u0430 \u043a\u043e\u0434\u0442\u0430\u0443 \u049b\u04b1\u0440\u0430\u043b\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0430\u0442\u044b\u043d \u0436\u04b1\u043c\u044b\u0441 \u0444\u0430\u0439\u043b\u0434\u0430\u0440\u044b \u0431\u0430\u0440. \u0422\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u044b \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437, \u043d\u0435\u043c\u0435\u0441\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u049b\u0430\u043b\u0442\u0430\u0441\u044b \u0456\u0448\u0456\u043d\u0434\u0435\u0433\u0456 \u04d9\u0434\u0435\u043f\u043a\u0456\u0441\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", "TabBasics": "\u041d\u0435\u0433\u0456\u0437\u0433\u0456\u043b\u0435\u0440", "TabTV": "\u0422\u0414", "TabGames": "\u041e\u0439\u044b\u043d\u0434\u0430\u0440", @@ -393,7 +393,7 @@ "HeaderAwardsAndReviews": "\u041c\u0430\u0440\u0430\u043f\u0430\u0442\u0442\u0430\u0440 \u043c\u0435\u043d \u043f\u0456\u043a\u0456\u0440\u043b\u0435\u0440", "HeaderSoundtracks": "\u0424\u0438\u043b\u044c\u043c\u0434\u0456\u04a3 \u043c\u0443\u0437\u044b\u043a\u0430\u0441\u044b", "HeaderMusicVideos": "\u0411\u0435\u0439\u043d\u0435\u043a\u043b\u0438\u043f\u0442\u0435\u0440", - "HeaderSpecialFeatures": "\u0410\u0440\u043d\u0430\u0439\u044b \u049b\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440", + "HeaderSpecialFeatures": "\u0410\u0440\u043d\u0430\u0439\u044b \u043c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440", "HeaderCastCrew": "\u0422\u04af\u0441\u0456\u0440\u0443\u0433\u0435 \u049b\u0430\u0442\u044b\u0441\u049b\u0430\u043d\u0434\u0430\u0440", "HeaderAdditionalParts": "\u0416\u0430\u043b\u0493\u0430\u0441\u0430\u0442\u044b\u043d \u0431\u04e9\u043b\u0456\u043c\u0434\u0435\u0440", "ButtonSplitVersionsApart": "\u041d\u04af\u0441\u049b\u0430\u043b\u0430\u0440\u0434\u044b \u049b\u0430\u0439\u0442\u0430 \u0431\u04e9\u043b\u0443", @@ -590,7 +590,7 @@ "NotificationOptionInstallationFailed": "\u041e\u0440\u043d\u0430\u0442\u0443 \u0441\u04d9\u0442\u0441\u0456\u0437\u0434\u0456\u0433\u0456", "NotificationOptionNewLibraryContent": "\u0416\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u04af\u0441\u0442\u0435\u043b\u0433\u0435\u043d", "NotificationOptionNewLibraryContentMultiple": "\u0416\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u049b\u043e\u0441\u044b\u043b\u0434\u044b (\u0431\u0456\u0440\u043d\u0435\u0448\u0435)", - "SendNotificationHelp": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u043c\u0430\u043b\u0430\u0440 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b\u043d\u0434\u0430\u0493\u044b \u04d9\u0434\u0435\u043f\u043a\u0456 \u043a\u0456\u0440\u0456\u0441 \u0436\u04d9\u0448\u0456\u0433\u0456\u043d\u0435 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u043b\u0435\u0434\u0456. \u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443 \u049b\u04b1\u0440\u0430\u043b\u0434\u0430\u0440\u044b\u043d \u043e\u0440\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0456\u043d \u0448\u0430\u0440\u043b\u0430\u04a3\u044b\u0437.", + "SendNotificationHelp": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b\u043d\u0434\u0430\u0493\u044b \u04d9\u0434\u0435\u043f\u043a\u0456 \u043a\u0456\u0440\u0456\u0441 \u0436\u04d9\u0448\u0456\u0433\u0456\u043d\u0435 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u043b\u0435\u0434\u0456. \u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443 \u049b\u04b1\u0440\u0430\u043b\u0434\u0430\u0440\u044b\u043d \u043e\u0440\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0456\u043d \u0448\u0430\u0440\u043b\u0430\u04a3\u044b\u0437.", "NotificationOptionServerRestartRequired": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u049b\u0430\u0439\u0442\u0430 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443 \u049b\u0430\u0436\u0435\u0442", "LabelNotificationEnabled": "\u0411\u04b1\u043b \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u043c\u0430\u043d\u044b \u049b\u043e\u0441\u0443", "LabelMonitorUsers": "\u041c\u044b\u043d\u0430\u043d\u044b\u04a3 \u04d9\u0440\u0435\u043a\u0435\u0442\u0442\u0435\u0440\u0456\u043d \u0431\u0430\u049b\u044b\u043b\u0430\u0443:", @@ -627,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": "Toggle fullscreen", + "ButtonFullscreen": "\u0422\u043e\u043b\u044b\u049b \u044d\u043a\u0440\u0430\u043d", "ButtonScenes": "\u0421\u0430\u0445\u043d\u0430\u043b\u0430\u0440", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "\u0414\u044b\u0431\u044b\u0441 \u0436\u043e\u043b\u0448\u044b\u049b\u0442\u0430\u0440\u044b", "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", @@ -642,7 +642,7 @@ "ButtonVolumeDown": "\u04ae\u043d\u0434\u0456\u043b\u0456\u043a\u0442\u0456 \u0442\u04e9\u043c\u0435\u043d\u0434\u0435\u0442\u0443", "ButtonMute": "\u0414\u044b\u0431\u044b\u0441\u0442\u044b \u04e9\u0448\u0456\u0440\u0443", "HeaderLatestMedia": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456 \u0442\u0430\u0441\u0443\u0448\u044b\u043b\u0430\u0440", - "OptionSpecialFeatures": "\u0410\u0440\u043d\u0430\u0439\u044b \u049b\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440", + "OptionSpecialFeatures": "\u0410\u0440\u043d\u0430\u0439\u044b \u043c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440", "HeaderCollections": "\u0422\u043e\u043f\u0442\u0430\u043c\u0430\u043b\u0430\u0440", "LabelProfileCodecsHelp": "\u04ae\u0442\u0456\u0440 \u0430\u0440\u049b\u044b\u043b\u044b \u0431\u04e9\u043b\u0456\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d. \u0411\u0430\u0440\u043b\u044b\u049b \u043a\u043e\u0434\u0435\u043a\u0442\u0435\u0440\u0433\u0435 \u049b\u043e\u043b\u0434\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043b \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u043b\u0443 \u043c\u04af\u043c\u043a\u0456\u043d.", "LabelProfileContainersHelp": "\u04ae\u0442\u0456\u0440 \u0430\u0440\u049b\u044b\u043b\u044b \u0431\u04e9\u043b\u0456\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d. \u0411\u0430\u0440\u043b\u044b\u049b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043b\u0435\u0440\u0433\u0435 \u049b\u043e\u043b\u0434\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043b \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u043b\u0443 \u043c\u04af\u043c\u043a\u0456\u043d.", @@ -800,7 +800,7 @@ "LabelEnableChannelContentDownloadingFor": "\u0411\u04b1\u043b \u04af\u0448\u0456\u043d \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u0441\u0443:", "LabelEnableChannelContentDownloadingForHelp": "\u041a\u0435\u0439\u0431\u0456\u0440 \u0430\u0440\u043d\u0430\u043b\u0430\u0440 \u049b\u0430\u0440\u0430\u0443\u0434\u044b\u04a3 \u0430\u043b\u0434\u044b\u043d\u0434\u0430 \u043c\u0430\u0437\u043c\u04af\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u043b\u0434\u0430\u0439\u0434\u044b. \u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u043b\u0430\u0440\u0434\u0430 \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04b1\u043c\u044b\u0441\u0442\u0430\u043d \u0431\u043e\u0441 \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043d\u044b \u049b\u043e\u0441\u044b\u04a3\u044b\u0437. \u041c\u0430\u0437\u043c\u04af\u043d \u0430\u0440\u043d\u0430 \u0436\u04af\u043a\u0442\u0435\u0443 \u0436\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d \u0442\u0430\u043f\u0441\u044b\u0440\u043c\u0430\u0441\u044b \u0431\u04e9\u043b\u0456\u0433\u0456 \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0436\u04af\u043a\u0442\u0435\u043b\u0435\u0434\u0456.", "LabelChannelDownloadPath": "\u0410\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u0436\u043e\u043b\u044b:", - "LabelChannelDownloadPathHelp": "\u041a\u0435\u0440\u0435\u043a \u0431\u043e\u043b\u0441\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u0436\u043e\u043b\u044b\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u04a3\u0456\u0437. \u0411\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430 \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u0456\u0448\u043a\u0456 \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u0430 \u0436\u04af\u043a\u0442\u0435\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", + "LabelChannelDownloadPathHelp": "\u041a\u0435\u0440\u0435\u043a \u0431\u043e\u043b\u0441\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u0436\u043e\u043b\u044b\u043d \u0430\u043d\u044b\u049b\u0442\u0430\u04a3\u044b\u0437. \u0411\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430 \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u0456\u0448\u043a\u0456 \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d\u0430 \u0436\u04af\u043a\u0442\u0435\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", "LabelChannelDownloadAge": "\u041c\u0430\u0437\u043c\u04b1\u043d \u0436\u043e\u0439\u044b\u043b\u0443\u044b \u043a\u0435\u043b\u0435\u0441\u0456\u0434\u0435\u043d \u043a\u0435\u0439\u0456\u043d, \u043a\u04af\u043d:", "LabelChannelDownloadAgeHelp": "\u0411\u04b1\u0434\u0430\u043d \u0431\u04b1\u0440\u044b\u043d\u0493\u044b \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0493\u0430\u043d \u043c\u0430\u0437\u043c\u04af\u043d \u0436\u043e\u0439\u044b\u043b\u0430\u0434\u044b. \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0430\u0440\u049b\u044b\u043b\u044b \u0430\u0493\u044b\u043d\u043c\u0435\u043d \u0442\u0430\u0441\u044b\u043c\u0430\u043b\u0434\u0430\u0443 \u04d9\u0434\u0456\u0441\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043f \u043e\u0439\u043d\u0430\u0442\u0443 \u0456\u0441\u0442\u0435 \u049b\u0430\u043b\u0430\u0434\u044b.", "ChannelSettingsFormHelp": "\u041f\u043b\u0430\u0433\u0438\u043d \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0456\u043d\u0434\u0435\u0433\u0456 Trailers \u0436\u04d9\u043d\u0435 Vimeo \u0441\u0438\u044f\u049b\u0442\u044b \u0430\u0440\u043d\u0430\u043b\u0430\u0440\u0434\u044b \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.", @@ -823,7 +823,7 @@ "LabelProtocolInfo": "\u041f\u0440\u043e\u0442\u043e\u049b\u043e\u043b \u0430\u049b\u043f\u0430\u0440\u0430\u0442\u044b:", "LabelProtocolInfoHelp": "\u0411\u04b1\u043b \u043c\u04d9\u043d \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u044b\u04a3 GetProtocolInfo \u0441\u04b1\u0440\u0430\u043d\u044b\u0441\u0442\u0430\u0440\u044b\u043d\u0430 \u0436\u0430\u0443\u0430\u043f \u0431\u0435\u0440\u0433\u0435\u043d\u0434\u0435 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b.", "TabXbmcMetadata": "Xbmc", - "HeaderXbmcMetadataHelp": "Media Browser \u0431\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430\u0441\u044b Xbmc Nfo \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u0436\u04d9\u043d\u0435 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u043a\u0456\u0440\u0456\u043a\u0442\u0456\u0440\u043c\u0435 \u049b\u043e\u043b\u0434\u0430\u0443\u044b\u043d \u049b\u0430\u043c\u0442\u0438\u0434\u044b.", + "HeaderXbmcMetadataHelp": "Media Browser \u0431\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430\u0441\u044b Xbmc Nfo \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u0436\u04d9\u043d\u0435 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u043a\u0456\u0440\u0456\u043a\u0442\u0456\u0440\u043c\u0435 \u049b\u043e\u043b\u0434\u0430\u0443\u044b\u043d \u049b\u0430\u043c\u0442\u0438\u0434\u044b. Xbmc \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0456\u043d \u049b\u043e\u0441\u0443 \u043d\u0435\u043c\u0435\u0441\u0435 \u04e9\u0448\u0456\u0440\u0443 \u04af\u0448\u0456\u043d \u049a\u044b\u0437\u043c\u0435\u0442\u0442\u0435\u0440 \u049b\u043e\u0439\u044b\u043d\u0434\u044b\u0441\u044b\u043d\u0434\u0430\u0493\u044b \u0442\u0430\u0441\u0443\u0448\u044b \u0442\u04af\u0440\u043b\u0435\u0440\u0456\u043d\u0435 \u0430\u0440\u043d\u0430\u043b\u0493\u0430\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0434\u0456 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u04a3\u044b\u0437.", "LabelXbmcMetadataUser": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043d\u044b\u04a3 \u049b\u0430\u0440\u0430\u0443 \u043a\u04af\u0439\u0456\u043d nfo \u04af\u0448\u0456\u043d \u043c\u044b\u043d\u0430\u0493\u0430\u043d \u049b\u043e\u0441\u0443:", "LabelXbmcMetadataUserHelp": "\u041a\u04e9\u0440\u0456\u043b\u0433\u0435\u043d \u043a\u04af\u0439\u0434\u0456 Media Browser \u0436\u04d9\u043d\u0435 Xbmc \u0430\u0440\u0430\u0441\u044b\u043d\u0434\u0430 \u04af\u0439\u043b\u0435\u0441\u0442\u0456\u0440\u0456\u043f \u0442\u04b1\u0440\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043d\u044b \u049b\u043e\u0441\u044b\u04a3\u044b\u0437.", "LabelXbmcMetadataDateFormat": "\u0416\u044b\u0493\u0430\u0440\u0443 \u043a\u04af\u043d-\u0430\u0439\u044b\u043d\u044b\u04a3 \u043f\u0456\u0448\u0456\u043c\u0456:", @@ -880,9 +880,13 @@ "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:", + "LabelPageSize": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0456\u04a3 \u0448\u0435\u0433\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" + "HeaderFeatures": "\u041c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440", + "HeaderAdvanced": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430", + "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" } \ 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 e1a405972..f474225fd 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ko.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ko.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 515cd70a2..232e94a72 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 c2afde015..c10e19102 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 4e01acfa1..c1a871095 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -627,7 +627,7 @@ "TabNowPlaying": "Wordt nu afgespeeld", "TabNavigation": "Navigatie", "TabControls": "Besturing", - "ButtonFullscreen": "Toggle fullscreen", + "ButtonFullscreen": "Schakelen tussen volledig scherm ", "ButtonScenes": "Scenes", "ButtonSubtitles": "Ondertitels", "ButtonAudioTracks": "Audio tracks", @@ -883,6 +883,10 @@ "LabelPageSize": "Schermgrootte:", "LabelView": "Weergave:", "TabUsers": "Gebruikers", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderFeatures": "Functies", + "HeaderAdvanced": "Geavanceerd", + "ButtonSync": "Sync", + "TabScheduledTasks": "Geplande taken", + "HeaderChapters": "Hoofdstukken", + "HeaderResumeSettings": "Resume Settings" } \ 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 b5b610654..3032f71f0 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 b635d7318..a80d352b9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -199,7 +199,7 @@ "OptionFriday": "Sexta-feira", "OptionSaturday": "S\u00e1bado", "HeaderManagement": "Gerenciamento:", - "LabelManagement": "Management:", + "LabelManagement": "Administra\u00e7\u00e3o:", "OptionMissingImdbId": "Faltando Id IMDb", "OptionMissingTvdbId": "Faltando Id TheTVDB", "OptionMissingOverview": "Faltando Sinopse", @@ -627,10 +627,10 @@ "TabNowPlaying": "Reproduzindo Agora", "TabNavigation": "Navega\u00e7\u00e3o", "TabControls": "Controles", - "ButtonFullscreen": "Toggle fullscreen", + "ButtonFullscreen": "Alternar para tela cheia", "ButtonScenes": "Cenas", "ButtonSubtitles": "Legendas", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "Faixas de \u00e1udio", "ButtonPreviousTrack": "Faixa anterior", "ButtonNextTrack": "Pr\u00f3xima faixa", "ButtonStop": "Parar", @@ -883,6 +883,10 @@ "LabelPageSize": "Tamanho de exibi\u00e7\u00e3o:", "LabelView": "Visualizar:", "TabUsers": "Usu\u00e1rios", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderFeatures": "Inclui", + "HeaderAdvanced": "Avan\u00e7ado", + "ButtonSync": "Sync", + "TabScheduledTasks": "Tarefas Agendadas", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 12850f803..83e82070a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 fcf3756ab..abdb98e97 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -122,8 +122,8 @@ "TabAlbumArtists": "\u0410\u043b\u044c\u0431\u043e\u043c\u043d\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u0438", "TabMusicVideos": "\u041a\u043b\u0438\u043f\u044b", "ButtonSort": "\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c", - "HeaderSortBy": "\u041f\u043e \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044e:", - "HeaderSortOrder": "\u041f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443:", + "HeaderSortBy": "\u041a\u0440\u0438\u0442\u0435\u0440\u0438\u0439:", + "HeaderSortOrder": "\u041f\u043e\u0440\u044f\u0434\u043e\u043a:", "OptionPlayed": "\u0412\u043e\u0441\u043f\u0440-\u043d\u043d\u044b\u0435", "OptionUnplayed": "\u041d\u0435 \u0432\u043e\u0441\u043f\u0440-\u043d\u043d\u044b\u0435", "OptionAscending": "\u0412\u043e\u0437\u0440\u0430\u0441\u0442\u0430\u043d\u0438\u0435", @@ -167,7 +167,7 @@ "OptionDvd": "DVD", "OptionIso": "ISO", "Option3D": "3D", - "LabelFeatures": "\u0414\u043b\u044f \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u0432:", + "LabelFeatures": "\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b:", "LabelService": "\u0421\u043b\u0443\u0436\u0431\u0430:", "LabelStatus": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435:", "LabelVersion": "\u0412\u0435\u0440\u0441\u0438\u044f:", @@ -185,12 +185,12 @@ "OptionImdbRating": "\u041e\u0446\u0435\u043d\u043a\u0430 IMDb", "OptionParentalRating": "\u0412\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u0430\u044f \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f", "OptionPremiereDate": "\u0414\u0430\u0442\u0430 \u043f\u0440\u0435\u043c\u044c\u0435\u0440\u044b", - "TabBasic": "\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435", - "TabAdvanced": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435", + "TabBasic": "\u041e\u0441\u043d\u043e\u0432\u044b", + "TabAdvanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e", "HeaderStatus": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", "OptionContinuing": "\u041f\u0440\u043e\u0434\u043b\u0435\u043d\u043e", "OptionEnded": "\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e", - "HeaderAirDays": "\u0414\u043d\u0438 \u044d\u0444\u0438\u0440\u0430:", + "HeaderAirDays": "\u0414\u043d\u0438 \u044d\u0444\u0438\u0440\u0430", "OptionSunday": "\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435", "OptionMonday": "\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a", "OptionTuesday": "\u0432\u0442\u043e\u0440\u043d\u0438\u043a", @@ -198,8 +198,8 @@ "OptionThursday": "\u0447\u0435\u0442\u0432\u0435\u0440\u0433", "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:", + "HeaderManagement": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435", + "LabelManagement": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435", "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", @@ -210,12 +210,12 @@ "TabAbout": "\u041e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435", "TabSupporterKey": "\u041a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430", "TabBecomeSupporter": "\u0421\u0442\u0430\u0442\u044c \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u043e\u043c", - "MediaBrowserHasCommunity": "Media Browser \u0438\u043c\u0435\u0435\u0442 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u044e\u0449\u0435\u0435\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.", - "CheckoutKnowledgeBase": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0411\u0430\u0437\u043e\u0439 \u0437\u043d\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u043e \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442 Media Browser.", + "MediaBrowserHasCommunity": "\u0423 Media Browser - \u0440\u0430\u0441\u0442\u0443\u0449\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432.", + "CheckoutKnowledgeBase": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0411\u0430\u0437\u043e\u0439 \u0437\u043d\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0441\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442 Media Browser.", "SearchKnowledgeBase": "\u0418\u0441\u043a\u0430\u0442\u044c \u0432 \u0411\u0430\u0437\u0435 \u0437\u043d\u0430\u043d\u0438\u0439", "VisitTheCommunity": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c \u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e", "VisitMediaBrowserWebsite": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c \u0441\u0430\u0439\u0442 Media Browser", - "VisitMediaBrowserWebsiteLong": "\u041f\u043e\u0441\u0435\u0449\u0430\u0439\u0442\u0435 \u0441\u0430\u0439\u0442 Media Browser, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043f\u0435\u0432\u0430\u0442\u044c \u0437\u0430 \u043d\u043e\u0432\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043b\u043e\u0433 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432.", + "VisitMediaBrowserWebsiteLong": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 \u0441\u0430\u0439\u0442 Media Browser, \u0447\u0442\u043e\u0431\u044b \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u043d\u043e\u0432\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043b\u043e\u0433\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432.", "OptionHideUser": "\u0421\u043a\u0440\u044b\u0442\u044c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u0432\u0445\u043e\u0434\u0430", "OptionDisableUser": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "OptionDisableUserHelp": "\u041f\u0440\u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0440\u0435\u0437\u043a\u043e \u043e\u0431\u043e\u0440\u0432\u0430\u043d\u044b.", @@ -238,13 +238,13 @@ "ButtonAddToCollection": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438", "PismoMessage": "Pismo File Mount \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u043f\u043e\u0434\u0430\u0440\u0435\u043d\u043d\u043e\u0439 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0438.", "TangibleSoftwareMessage": "Tangible Solutions Java\/C# \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u043f\u043e\u0434\u0430\u0440\u0435\u043d\u043d\u043e\u0439 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0438.", - "HeaderCredits": "\u0412\u043d\u0451\u0441\u0448\u0438\u0435 \u0432\u043a\u043b\u0430\u0434", + "HeaderCredits": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", "PleaseSupportOtherProduces": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0442\u0435 \u0442\u0430\u043a\u0436\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c:", "VersionNumber": "\u0412\u0435\u0440\u0441\u0438\u044f {0}", "TabPaths": "\u041f\u0443\u0442\u0438", "TabServer": "\u0421\u0435\u0440\u0432\u0435\u0440", "TabTranscoding": "\u041f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430", - "TitleAdvanced": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b", + "TitleAdvanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e", "LabelAutomaticUpdateLevel": "\u0421\u0442\u0435\u043f\u0435\u043d\u044c \u0430\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f", "OptionRelease": "\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u043f\u0443\u0441\u043a", "OptionBeta": "\u0411\u0435\u0442\u0430-\u0432\u0435\u0440\u0441\u0438\u044f", @@ -255,15 +255,15 @@ "LabelRunServerAtStartup": "\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b", "LabelRunServerAtStartupHelp": "\u0417\u043d\u0430\u0447\u043e\u043a \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u043c \u043b\u043e\u0442\u043a\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0441\u0442\u0430\u0440\u0442\u0430 Windows. \u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u043b\u0443\u0436\u0431\u0443 Windows, \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0433\u0430\u043b\u043e\u0447\u043a\u0443 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u0443 \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f Windows. \u041f\u0440\u0438\u043c\u0438\u0442\u0435 \u043a \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044e, \u0447\u0442\u043e \u043e\u043d\u0438 \u043e\u0431\u0430 \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u043e\u043a \u0432 \u043b\u043e\u0442\u043a\u0435 \u0434\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u043b\u0443\u0436\u0431\u044b.", "ButtonSelectDirectory": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u0442\u0430\u043b\u043e\u0433", - "LabelCustomPaths": "\u041d\u0430\u0437\u043d\u0430\u0447\u044c\u0442\u0435 \u043f\u0443\u0442\u0438 \u0442\u0443\u0434\u0430, \u043a\u0443\u0434\u0430 \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e. \u041e\u0441\u0442\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u043f\u043e\u043b\u044f \u043f\u0443\u0441\u0442\u044b\u043c\u0438 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.", + "LabelCustomPaths": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0435 \u043f\u0443\u0442\u0438 \u043f\u043e \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043c. \u041e\u0441\u0442\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u043f\u043e\u043b\u044f \u043f\u0443\u0441\u0442\u044b\u043c\u0438 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.", "LabelCachePath": "\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435 Cache:", - "LabelCachePathHelp": "\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u043a\u044d\u0448\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0438\u0441\u0443\u043d\u043a\u0438.", + "LabelCachePathHelp": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u043a\u044d\u0448\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432.", "LabelImagesByNamePath": "\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435 Images by name:", - "LabelImagesByNamePathHelp": "\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0430\u043a\u0442\u0451\u0440\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u0435\u0439 \u0436\u0430\u043d\u0440\u043e\u0432 \u0438 \u0441\u0442\u0443\u0434\u0438\u0439.", + "LabelImagesByNamePathHelp": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0445 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0430\u043a\u0442\u0451\u0440\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u0435\u0439, \u0436\u0430\u043d\u0440\u043e\u0432 \u0438 \u0441\u0442\u0443\u0434\u0438\u0439.", "LabelMetadataPath": "\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435 Metadata:", - "LabelMetadataPathHelp": "\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u0435 \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432 \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0430\u0445.", + "LabelMetadataPathHelp": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0445 \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0439 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043d\u0435 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0430\u0445.", "LabelTranscodingTempPath": "\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435 Transcoding temporary:", - "LabelTranscodingTempPathHelp": "\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0440\u0430\u0431\u043e\u0447\u0438\u0435 \u0444\u0430\u0439\u043b\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435. \u041d\u0430\u0437\u043d\u0430\u0447\u044c\u0442\u0435 \u043f\u0443\u0442\u044c, \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u043f\u0430\u043f\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", + "LabelTranscodingTempPathHelp": "\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0440\u0430\u0431\u043e\u0447\u0438\u0435 \u0444\u0430\u0439\u043b\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435. \u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c, \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u043f\u0430\u043f\u043a\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", "TabBasics": "\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435", "TabTV": "\u0422\u0412", "TabGames": "\u0418\u0433\u0440\u044b", @@ -277,9 +277,9 @@ "LabelAutomaticUpdatesFanart": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441 FanArt.tv", "LabelAutomaticUpdatesTmdb": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441 TheMovieDB.org", "LabelAutomaticUpdatesTvdb": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441 TheTVDB.com", - "LabelAutomaticUpdatesFanartHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 fanart.tv. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", - "LabelAutomaticUpdatesTmdbHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 TheMovieDB.org. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", - "LabelAutomaticUpdatesTvdbHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 TheTVDB.com. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", + "LabelAutomaticUpdatesFanartHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 fanart.tv. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", + "LabelAutomaticUpdatesTmdbHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 TheMovieDB.org. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", + "LabelAutomaticUpdatesTvdbHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043d\u043e\u0432\u044b\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 TheTVDB.com. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f.", "ExtractChapterImagesHelp": "\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0441\u0446\u0435\u043d \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043c\u0435\u043d\u044e \u0432\u044b\u0431\u043e\u0440\u0430 \u0441\u0446\u0435\u043d\u044b. \u0414\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u043c, \u043d\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440 \u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0433\u0438\u0433\u0430\u0431\u0430\u0439\u0442 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430. \u041e\u043d \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043a\u0430\u043a \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 4:00 \u0443\u0442\u0440\u0430, \u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0432 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430. \u041d\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u043e\u0435 \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u0432 \u0447\u0430\u0441\u044b \u043f\u0438\u043a.", "LabelMetadataDownloadLanguage": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044f\u0437\u044b\u043a\u0430 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445:", "ButtonAutoScroll": "\u0410\u0432\u0442\u043e\u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430", @@ -337,7 +337,7 @@ "OptionAutomatic": "\u0410\u0432\u0442\u043e", "LiveTvPluginRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043b\u0430\u0433\u0438\u043d-\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0443\u0441\u043b\u0443\u0433 \u044d\u0444\u0438\u0440\u043d\u043e\u0433\u043e \u0422\u0412 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u044f.", "LiveTvPluginRequiredHelp": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, NextPVR \u0438\u043b\u0438 ServerWMC.", - "LabelCustomizeOptionsPerMediaType": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e \u0442\u0438\u043f\u0443 \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044f:", + "LabelCustomizeOptionsPerMediaType": "\u041f\u043e\u0434\u0433\u043e\u043d\u043a\u0430 \u043f\u043e \u0442\u0438\u043f\u0443 \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044f:", "OptionDownloadThumbImage": "\u0411\u0435\u0433\u0443\u043d\u043e\u043a", "OptionDownloadMenuImage": "\u041c\u0435\u043d\u044e", "OptionDownloadLogoImage": "\u041b\u043e\u0433\u043e\u0442\u0438\u043f", @@ -411,7 +411,7 @@ "OptionSpecialEpisode": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435", "OptionMissingEpisode": "\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d. \u044d\u043f\u0438\u0437\u043e\u0434\u044b", "OptionUnairedEpisode": "\u041e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0435 \u044d\u043f\u0438\u0437\u043e\u0434\u044b", - "OptionEpisodeSortName": "\u0418\u043c\u044f \u0441\u043e\u0440\u0442-\u043a\u0438 \u044d\u043f\u0438\u0437\u043e\u0434\u0430", + "OptionEpisodeSortName": "\u0421\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0435 \u043d\u0430\u0437\u0432-\u0438\u0435 \u044d\u043f\u0438\u0437\u043e\u0434\u0430", "OptionSeriesSortName": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0438\u0430\u043b\u0430", "OptionTvdbRating": "\u041e\u0446\u0435\u043d\u043a\u0430 TVDb", "HeaderTranscodingQualityPreference": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438:", @@ -428,16 +428,16 @@ "OptionUpscalingHelp": "\u0412 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0451\u0442 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u0438\u0434\u0435\u043e, \u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u0441\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440.", "EditCollectionItemsHelp": "\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u0438\u043b\u0438 \u0443\u0431\u0438\u0440\u0430\u0439\u0442\u0435 \u043b\u044e\u0431\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b, \u0441\u0435\u0440\u0438\u0430\u043b\u044b, \u0430\u043b\u044c\u0431\u043e\u043c\u044b, \u043a\u043d\u0438\u0433\u0438 \u0438\u043b\u0438 \u0438\u0433\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e.", "HeaderAddTitles": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u044f", - "LabelEnableDlnaPlayTo": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DLNA-\u0440\u0435\u0436\u0438\u043c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445", + "LabelEnableDlnaPlayTo": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e DLNA \u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u041d\u0430", "LabelEnableDlnaPlayToHelp": "Media Browser \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u0441\u0435\u0442\u0438 \u0438 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043c\u0438.", "LabelEnableDlnaDebugLogging": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 DLNA \u0432 \u0416\u0443\u0440\u043d\u0430\u043b\u0435", "LabelEnableDlnaDebugLoggingHelp": "\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0416\u0443\u0440\u043d\u0430\u043b\u0430 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043e\u0431\u044a\u0451\u043c\u0430, \u0430 \u044d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u0438\u043b\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043f\u043e\u043b\u0430\u0434\u043e\u043a.", "LabelEnableDlnaClientDiscoveryInterval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u0441", "LabelEnableDlnaClientDiscoveryIntervalHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 SSDP \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u043c\u0438 Media Browser.", - "HeaderCustomDlnaProfiles": "\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438", + "HeaderCustomDlnaProfiles": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438", "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.", + "CustomDlnaProfilesHelp": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\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 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u043f\u0440\u043e\u0444\u0438\u043b\u0435.", "TitleDashboard": "\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c", "TabHome": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f", "TabInfo": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f", @@ -553,7 +553,7 @@ "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.", "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\u0434\u0435\u043d\u0438\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435", + "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", "LabelEnableDlnaServerHelp": "\u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 UPnP-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0432 \u0441\u0435\u0442\u0438 \u043e\u0431\u0437\u043e\u0440 \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f Media Browser.", "LabelEnableBlastAliveMessages": "\u0423\u0447\u0430\u0449\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438", @@ -562,7 +562,7 @@ "LabelBlastMessageIntervalHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", "LabelDefaultUser": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", "LabelDefaultUserHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u0447\u044c\u044f \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445. \u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439.", - "TitleDlna": "DLNA-\u0440\u0435\u0436\u0438\u043c", + "TitleDlna": "DLNA \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f", "TitleChannels": "\u041a\u0430\u043d\u0430\u043b\u044b", "HeaderServerSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0430", "LabelWeatherDisplayLocation": "\u041f\u043e\u0433\u043e\u0434\u0430 \u0434\u043b\u044f \u043c\u0435\u0441\u0442\u043d\u043e\u0441\u0442\u0438:", @@ -606,7 +606,7 @@ "AdditionalNotificationServices": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \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\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.", "OptionAllUsers": "\u0412\u0441\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", "OptionAdminUsers": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b", - "OptionCustomUsers": "\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0435", + "OptionCustomUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435", "ButtonArrowUp": "\u0412\u0432\u0435\u0440\u0445", "ButtonArrowDown": "\u0412\u043d\u0438\u0437", "ButtonArrowLeft": "\u0412\u043b\u0435\u0432\u043e", @@ -627,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": "Toggle fullscreen", + "ButtonFullscreen": "\u041f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d", "ButtonScenes": "\u0421\u0446\u0435\u043d\u044b", "ButtonSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b", - "ButtonAudioTracks": "Audio tracks", + "ButtonAudioTracks": "\u0410\u0443\u0434\u0438\u043e \u0434\u043e\u0440\u043e\u0436\u043a\u0438", "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", @@ -663,7 +663,7 @@ "OptionProfileVideoAudio": "\u0412\u0438\u0434\u0435\u043e \u0410\u0443\u0434\u0438\u043e", "OptionProfilePhoto": "\u0424\u043e\u0442\u043e", "LabelUserLibrary": "\u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f:", - "LabelUserLibraryHelp": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u0447\u044c\u044e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0443 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.", + "LabelUserLibraryHelp": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u0447\u044c\u044e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0443 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.", "OptionPlainStorageFolders": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u043f\u0430\u043f\u043a\u0438, \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043f\u0430\u043f\u043a\u0438 \u0445\u0440\u0430\u043d\u0438\u0435\u043d\u0438\u044f", "OptionPlainStorageFoldersHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u0432\u0441\u0435 \u043f\u0430\u043f\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0432 DIDL \u043a\u0430\u043a \"object.container.storageFolder\", \u0432\u043c\u0435\u0441\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \"object.container.person.musicArtist\".", "OptionPlainVideoItems": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b, \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0432\u0438\u0434\u0435\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b", @@ -688,7 +688,7 @@ "LabelIconMaxHeight": "\u041c\u0430\u043a\u0441. \u0432\u044b\u0441\u043e\u0442\u0430 \u0437\u043d\u0430\u0447\u043a\u0430:", "LabelIconMaxHeightHelp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u043d\u0430\u0447\u043a\u043e\u0432 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0445 \u0447\u0435\u0440\u0435\u0437 upnp:icon.", "LabelIdentificationFieldHelp": "\u041f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0430 \u0431\u0435\u0437 \u0443\u0447\u0451\u0442\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430, \u043b\u0438\u0431\u043e \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435.", - "HeaderProfileServerSettingsHelp": "\u042d\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442, \u043a\u0430\u043a Media Browser \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0435\u0431\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435.", + "HeaderProfileServerSettingsHelp": "\u042d\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442, \u043a\u0430\u043a Media Browser \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435.", "LabelMaxBitrate": "\u041c\u0430\u043a\u0441. \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u0430\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c:", "LabelMaxBitrateHelp": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u0443\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043b\u0438\u0431\u043e \u0435\u0441\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u0451 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435.", "OptionIgnoreTranscodeByteRangeRequests": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430 \u0431\u0430\u0439\u0442\u043e\u0432 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438", @@ -705,7 +705,7 @@ "HeaderIdentificationCriteriaHelp": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435, \u043f\u043e \u043a\u0440\u0430\u0439\u043d\u0435\u0439 \u043c\u0435\u0440\u0435, \u043e\u0434\u0438\u043d \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0439.", "HeaderDirectPlayProfileHelp": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0443\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e.", "HeaderTranscodingProfileHelp": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0443\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430.", - "HeaderResponseProfileHelp": "\u041f\u0440\u043e\u0444\u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u043e\u0432 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043f\u043e\u0441\u044b\u043b\u0430\u0435\u043c\u043e\u0439 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u0440\u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0432\u0438\u0434\u043e\u0432 \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u0439.", + "HeaderResponseProfileHelp": "\u041f\u0440\u043e\u0444\u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u043e\u0432 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043e\u0433\u043d\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u043f\u043e\u0441\u044b\u043b\u0430\u0435\u043c\u0443\u044e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u0440\u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0432\u0438\u0434\u043e\u0432 \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u0439.", "LabelXDlnaCap": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 X-Dlna:", "LabelXDlnaCapHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 X_DLNACAP \u0432 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435 \u0438\u043c\u0451\u043d urn:schemas-dlna-org:device-1-0", "LabelXDlnaDoc": "\u0421\u0445\u0435\u043c\u0430 X-Dlna:", @@ -738,7 +738,7 @@ "ButtonRegister": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f", "LabelSkipIfAudioTrackPresent": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u044f\u0437\u044b\u043a \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u044f\u0437\u044b\u043a\u043e\u043c \u0430\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0438 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", "LabelSkipIfAudioTrackPresentHelp": "\u0421\u043d\u044f\u0442\u044c \u0444\u043b\u0430\u0436\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u0432\u0438\u0434\u0435\u043e \u0438\u043c\u0435\u043b\u0438\u0441\u044c \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044b, \u0432\u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u044f\u0437\u044b\u043a\u0430 \u0430\u0443\u0434\u0438\u043e.", - "HeaderSendMessage": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435", + "HeaderSendMessage": "\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f", "ButtonSend": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c", "LabelMessageText": "\u0422\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f:", "MessageNoAvailablePlugins": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432.", @@ -763,7 +763,7 @@ "LabelEnableThemeSongsHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043c\u0435\u043b\u043e\u0434\u0438\u0438 \u0442\u0435\u043c \u0431\u0443\u0434\u0443\u0442 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u0438 \u043e\u0431\u0437\u043e\u0440\u0435 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438.", "LabelEnableBackdropsHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u0437\u0430\u0434\u043d\u0438\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0444\u043e\u043d \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043f\u0440\u0438 \u043e\u0431\u0437\u043e\u0440\u0435 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438.", "HeaderHomePage": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430", - "HeaderSettingsForThisDevice": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "HeaderSettingsForThisDevice": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "OptionAuto": "\u0410\u0432\u0442\u043e", "OptionYes": "\u0414\u0430", "OptionNo": "\u041d\u0435\u0442", @@ -792,18 +792,18 @@ "MessageNoCollectionsAvailable": "\u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u043c \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0438\u0437 \u0444\u0438\u043b\u044c\u043c\u043e\u0432, \u0441\u0435\u0440\u0438\u0430\u043b\u043e\u0432, \u0430\u043b\u044c\u0431\u043e\u043c\u043e\u0432 \u0438 \u0438\u0433\u0440. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \"\u0421\u043e\u0437\u0434\u0430\u0442\u044c\", \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e.", "HeaderWelcomeToMediaBrowserWebClient": "\u0412\u0435\u0431-\u043a\u043b\u0438\u0435\u043d\u0442 Media Browser \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432\u0430\u0441!", "ButtonDismiss": "\u0421\u043a\u0440\u044b\u0442\u044c", - "MessageLearnHowToCustomize": "\u0423\u0437\u043d\u0430\u0439\u0442\u0435, \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c\u0443 \u0432\u043a\u0443\u0441\u0443. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u0430\u0432\u0430\u0442\u0430\u0440\u0443 \u0432 \u043f\u0440\u0430\u0432\u043e\u043c \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0433\u043b\u0443 \u044d\u043a\u0440\u0430\u043d\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", + "MessageLearnHowToCustomize": "\u0418\u0437\u0443\u0447\u0438\u0442\u0435, \u043a\u0430\u043a \u043f\u043e\u0434\u043e\u0433\u043d\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e \u0441\u0432\u043e\u0435\u043c\u0443 \u0432\u043a\u0443\u0441\u0443. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0430\u0432\u0430\u0442\u0430\u0440\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u043f\u0440\u0430\u0432\u043e\u043c \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0433\u043b\u0443 \u044d\u043a\u0440\u0430\u043d\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", "ButtonEditOtherUserPreferences": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c \u043b\u0438\u0447\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "LabelChannelStreamQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442:", "LabelChannelStreamQualityHelp": "\u0412 \u0441\u0440\u0435\u0434\u0435 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.", "OptionBestAvailableStreamQuality": "\u041d\u0430\u0438\u043b\u0443\u0447\u0448\u0435\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435", "LabelEnableChannelContentDownloadingFor": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430 \u0434\u043b\u044f:", - "LabelEnableChannelContentDownloadingForHelp": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435. \u0412\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043f\u0440\u0438 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0432\u0440\u0435\u043c\u044f. \u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u043d\u0430\u043b\u043e\u0432.", + "LabelEnableChannelContentDownloadingForHelp": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0434\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430. \u0412\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043f\u0440\u0438 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0432\u0440\u0435\u043c\u044f. \u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u043d\u0430\u043b\u043e\u0432.", "LabelChannelDownloadPath": "\u041f\u0443\u0442\u044c \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u043e\u0432:", - "LabelChannelDownloadPathHelp": "\u041d\u0430\u0437\u043d\u0430\u0447\u044c\u0442\u0435 \u0436\u0435\u043b\u0430\u0435\u043c\u044b\u0439 \u043f\u0443\u0442\u044c \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0432\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u043f\u0430\u043f\u043a\u0443 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.", + "LabelChannelDownloadPathHelp": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435, \u043f\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u044e, \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u043a. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0432\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u043f\u0430\u043f\u043a\u0443 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.", "LabelChannelDownloadAge": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437, \u0434\u043d\u0438:", - "LabelChannelDownloadAgeHelp": "\u0411\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043b\u0435\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0440\u0435\u0435, \u0447\u0435\u043c \u0434\u0430\u043d\u043d\u043e\u0435. \u041e\u043d\u043e \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u043c\u044b\u043c \u043f\u0440\u0438 \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442.", - "ChannelSettingsFormHelp": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0438\u0437 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Trailers \u0438 Vimeo.", + "LabelChannelDownloadAgeHelp": "\u0411\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043b\u0435\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0440\u0435\u0435, \u0447\u0435\u043c \u0434\u0430\u043d\u043d\u043e\u0435. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0435\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f.", + "ChannelSettingsFormHelp": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043a\u0430\u043d\u0430\u043b\u044b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Trailers \u0438 Vimeo) \u0438\u0437 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432.", "LabelSelectCollection": "\u0412\u044b\u0431\u043e\u0440 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438:", "ViewTypeMovies": "\u041a\u0438\u043d\u043e", "ViewTypeTvShows": "\u0422\u0412", @@ -816,7 +816,7 @@ "HeaderMyViews": "\u041c\u043e\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f", "LabelSelectFolderGroups": "\u0410\u0432\u0442\u043e\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u041a\u0438\u043d\u043e, \u041c\u0443\u0437\u044b\u043a\u0430 \u0438 \u0422\u0412) \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0430\u043f\u043e\u043a:", "LabelSelectFolderGroupsHelp": "\u041f\u0430\u043f\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u044b, \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u043f\u0440\u0438\u0441\u0443\u0449\u0438\u043c \u0438\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043c.", - "OptionDisplayAdultContent": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \"\u0434\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445\"", + "OptionDisplayAdultContent": "\u041e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \"\u0434\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445\"", "OptionLibraryFolders": "\u041c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0438", "TitleRemoteControl": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435", "OptionLatestTvRecordings": "\u041d\u043e\u0432\u0438\u043d\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439", @@ -831,7 +831,7 @@ "LabelXbmcMetadataSaveImagePaths": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0443\u0442\u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432 NFO \u0444\u0430\u0439\u043b\u0430\u0445", "LabelXbmcMetadataSaveImagePathsHelp": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u043d\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u044f\u0449\u0438\u043c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c XBMC.", "LabelXbmcMetadataEnablePathSubstitution": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439", - "LabelXbmcMetadataEnablePathSubstitutionHelp": "\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439 \u043a \u0440\u0438\u0441\u0443\u043d\u043a\u0430\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a \u043f\u0443\u0442\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439 \u043a \u0440\u0438\u0441\u0443\u043d\u043a\u0430\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a \u043f\u0443\u0442\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.", "LabelXbmcMetadataEnablePathSubstitutionHelp2": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439.", "LabelGroupChannelsIntoViews": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432 \u041c\u043e\u0438\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u044b:", "LabelGroupChannelsIntoViewsHelp": "\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043d\u0430\u0440\u044f\u0434\u0443 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438. \u041f\u0440\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u041a\u0430\u043d\u0430\u043b\u044b.", @@ -842,7 +842,7 @@ "TabLogs": "\u0416\u0443\u0440\u043d\u0430\u043b\u044b", "HeaderServerLogFiles": "\u0424\u0430\u0439\u043b\u044b \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430:", "TabBranding": "\u0411\u0440\u0435\u043d\u0434\u0438\u043d\u0433", - "HeaderBrandingHelp": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\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.", + "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", @@ -856,7 +856,7 @@ "LabelImagesByName": "Images by name:", "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", "HeaderLatestMusic": "\u041d\u043e\u0432\u0438\u043d\u043a\u0438 \u043c\u0443\u0437\u044b\u043a\u0438", - "HeaderBranding": "\u0411\u0440\u0435\u043d\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435", + "HeaderBranding": "\u0411\u0440\u0435\u043d\u0434\u0438\u043d\u0433", "HeaderApiKeys": "\u041a\u043b\u044e\u0447\u0438 API", "HeaderApiKeysHelp": "\u0412\u043d\u0435\u0448\u043d\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043a\u043b\u044e\u0447 API, \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Media Browser. \u041a\u043b\u044e\u0447\u0438 \u0432\u044b\u0434\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0432\u0445\u043e\u0434\u0435 \u0441 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e Media Browser, \u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e.", "HeaderApiKey": "\u041a\u043b\u044e\u0447 API", @@ -873,16 +873,20 @@ "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", + "OptionEquals": "\u0420\u0430\u0432\u043d\u043e", + "OptionRegex": "\u0420\u0435\u0433. \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435", "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", + "TabSort": "\u0421\u043e\u0440\u0442-\u043a\u0430", + "TabFilter": "\u0424\u0438\u043b\u044c\u0442\u0440-\u043a\u0430", "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:", + "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:", "TabUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", - "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "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", + "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" } \ 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 4a23b0c07..3a49024f9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -900,5 +900,8 @@ "TabUsers": "Users", "HeaderFeatures": "Features", "HeaderAdvanced": "Advanced", - "ButtonSync": "Sync" + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index e28dfe231..9988af0fa 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 78f308b33..98f108421 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 e67aa9f58..6511dd7d0 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -884,5 +884,9 @@ "LabelView": "View:", "TabUsers": "Users", "HeaderFeatures": "Features", - "HeaderAdvanced": "Advanced" + "HeaderAdvanced": "Advanced", + "ButtonSync": "Sync", + "TabScheduledTasks": "Scheduled Tasks", + "HeaderChapters": "Chapters", + "HeaderResumeSettings": "Resume Settings" } \ 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 38600922b..38e82db4f 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -224,6 +224,7 @@ + @@ -269,6 +270,8 @@ + + diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationException.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationException.cs new file mode 100644 index 000000000..f4ba2fb63 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Security/AuthenticationException.cs @@ -0,0 +1,16 @@ +using System; + +namespace MediaBrowser.Server.Implementations.Security +{ + public class AuthenticationException : Exception + { + public AuthenticationException(string message) + : base(message) + { + } + + public AuthenticationException() + { + } + } +} diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 754f63cbd..168948821 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -27,6 +27,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Server.Implementations.Security; namespace MediaBrowser.Server.Implementations.Session { @@ -232,7 +233,7 @@ namespace MediaBrowser.Server.Implementations.Session if (user != null && user.Configuration.IsDisabled) { - throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); } var activityDate = DateTime.UtcNow; @@ -1111,12 +1112,7 @@ namespace MediaBrowser.Server.Implementations.Session { var session = GetSession(sessionId); - if (!session.UserId.HasValue) - { - throw new UnauthorizedAccessException("Cannot modify additional users without authenticating first."); - } - - if (session.UserId.Value == userId) + if (session.UserId.HasValue && session.UserId.Value == userId) { throw new ArgumentException("The requested user is already the primary user of the session."); } @@ -1144,12 +1140,7 @@ namespace MediaBrowser.Server.Implementations.Session { var session = GetSession(sessionId); - if (!session.UserId.HasValue) - { - throw new UnauthorizedAccessException("Cannot modify additional users without authenticating first."); - } - - if (session.UserId.Value == userId) + if (session.UserId.HasValue && session.UserId.Value == userId) { throw new ArgumentException("The requested user is already the primary user of the session."); } @@ -1166,7 +1157,7 @@ namespace MediaBrowser.Server.Implementations.Session { if (string.IsNullOrWhiteSpace(token)) { - throw new UnauthorizedAccessException(); + throw new AuthenticationException(); } var result = _authRepo.Get(new AuthenticationInfoQuery @@ -1178,12 +1169,12 @@ namespace MediaBrowser.Server.Implementations.Session if (info == null) { - throw new UnauthorizedAccessException(); + throw new AuthenticationException(); } if (!info.IsActive) { - throw new UnauthorizedAccessException("Access token has expired."); + throw new AuthenticationException("Access token has expired."); } if (!string.IsNullOrWhiteSpace(info.UserId)) @@ -1192,7 +1183,7 @@ namespace MediaBrowser.Server.Implementations.Session if (user == null || user.Configuration.IsDisabled) { - throw new UnauthorizedAccessException("User account has been disabled."); + throw new AuthenticationException("User account has been disabled."); } } } @@ -1225,7 +1216,7 @@ namespace MediaBrowser.Server.Implementations.Session if (!result) { - throw new UnauthorizedAccessException("Invalid user or password entered."); + throw new AuthenticationException("Invalid user or password entered."); } var user = _userManager.Users diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs new file mode 100644 index 000000000..6dec2b717 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs @@ -0,0 +1,26 @@ +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class AppSyncProvider : ISyncProvider + { + public IEnumerable GetSyncTargets() + { + throw new NotImplementedException(); + } + + public DeviceProfile GetDeviceProfile(SyncTarget target) + { + throw new NotImplementedException(); + } + + public string Name + { + get { return "App Sync"; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs new file mode 100644 index 000000000..ec3ac1239 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Common; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class CloudSyncProvider : ISyncProvider + { + private ICloudSyncProvider[] _providers = new ICloudSyncProvider[] {}; + + public CloudSyncProvider(IApplicationHost appHost) + { + _providers = appHost.GetExports().ToArray(); + } + + public IEnumerable GetSyncTargets() + { + throw new NotImplementedException(); + } + + public DeviceProfile GetDeviceProfile(SyncTarget target) + { + throw new NotImplementedException(); + } + + public string Name + { + get { return "Cloud Sync"; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 373b30a41..508fc188d 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -3,12 +3,20 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Sync { public class SyncManager : ISyncManager { + private ISyncProvider[] _providers = new ISyncProvider[]{}; + + public void AddParts(IEnumerable providers) + { + _providers = providers.ToArray(); + } + public Task> CreateJob(SyncJobRequest request) { throw new NotImplementedException(); @@ -48,5 +56,10 @@ namespace MediaBrowser.Server.Implementations.Sync { throw new NotImplementedException(); } + + public IEnumerable GetSyncTargets() + { + return _providers.SelectMany(i => i.GetSyncTargets()); + } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 7c36a7c5e..2208bc217 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -188,6 +188,7 @@ namespace MediaBrowser.ServerApplication private IEncodingManager EncodingManager { get; set; } private IChannelManager ChannelManager { get; set; } + private ISyncManager SyncManager { get; set; } /// /// Gets or sets the user data repository. @@ -613,7 +614,8 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); - RegisterSingleInstance(new SyncManager()); + SyncManager = new SyncManager(); + RegisterSingleInstance(SyncManager); var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); @@ -823,6 +825,7 @@ namespace MediaBrowser.ServerApplication ChannelManager.AddParts(GetExports(), GetExports()); NotificationManager.AddParts(GetExports(), GetExports()); + SyncManager.AddParts(GetExports()); } /// @@ -1139,15 +1142,16 @@ namespace MediaBrowser.ServerApplication var versionObject = version == null || string.IsNullOrWhiteSpace(version.versionStr) ? null : new Version(version.versionStr); - HasUpdateAvailable = versionObject != null && versionObject >= ApplicationVersion; + var isUpdateAvailable = versionObject != null && versionObject > ApplicationVersion; + HasUpdateAvailable = isUpdateAvailable; - if (versionObject != null && versionObject >= ApplicationVersion) + if (isUpdateAvailable) { Logger.Info("New application version is available: {0}", versionObject); } - return versionObject != null ? - new CheckForUpdateResult { AvailableVersion = versionObject.ToString(), IsUpdateAvailable = versionObject > ApplicationVersion, Package = version } : + return versionObject != null ? + new CheckForUpdateResult { AvailableVersion = versionObject.ToString(), IsUpdateAvailable = isUpdateAvailable, Package = version } : new CheckForUpdateResult { AvailableVersion = ApplicationVersion.ToString(), IsUpdateAvailable = false }; } diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs index 206a04460..eb73966ce 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg switch (arg) { case "Version": - return "20140612"; + return "20140721"; case "FFMpegFilename": return "ffmpeg.exe"; case "FFProbeFilename": @@ -113,8 +113,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg case PlatformID.Win32NT: return new[] { - "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140612-git-3a1c895-win32-static.7z", - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140612-git-3a1c895-win32-static.7z" + "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140721-git-ce385c8-win32-static.7z", + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140721-git-ce385c8-win32-static.7z" }; case PlatformID.Unix: diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs index c550cb27f..a93ed9196 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs @@ -26,10 +26,10 @@ namespace MediaBrowser.ServerApplication.FFMpeg private readonly IZipClient _zipClient; private readonly IFileSystem _fileSystem; - private readonly string[] _fontUrls = new[] - { - "https://www.dropbox.com/s/pj847twf7riq0j7/ARIALUNI.7z?dl=1" - }; + private readonly string[] _fontUrls = + { + "https://www.dropbox.com/s/pj847twf7riq0j7/ARIALUNI.7z?dl=1" + }; public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem) { @@ -251,6 +251,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg Task.Run(async () => { await DownloadFontFile(fontsDirectory, fontFilename, new Progress()).ConfigureAwait(false); + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); }); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 64c055ba0..92487959d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -450,7 +450,12 @@ namespace MediaBrowser.WebDashboard.Api 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); - await AppendResource(memoryStream, "thirdparty/cast_sender.js", newLineBytes).ConfigureAwait(false); + + // This script produces errors in older versions of safari + if ((Request.UserAgent ?? string.Empty).IndexOf("chrome/", StringComparison.OrdinalIgnoreCase) != -1) + { + await AppendResource(memoryStream, "thirdparty/cast_sender.js", newLineBytes).ConfigureAwait(false); + } await AppendLocalization(memoryStream).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); @@ -535,7 +540,6 @@ namespace MediaBrowser.WebDashboard.Api "addpluginpage.js", "advancedconfigurationpage.js", "metadataadvanced.js", - "appsplayback.js", "autoorganizetv.js", "autoorganizelog.js", "channels.js", @@ -591,7 +595,6 @@ namespace MediaBrowser.WebDashboard.Api "metadataconfigurationpage.js", "metadataimagespage.js", "metadatasubtitles.js", - "metadatachapters.js", "metadataxbmc.js", "moviegenres.js", "moviecollections.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 06a44d747..25cff11cf 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -89,9 +89,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -323,13 +320,13 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -584,9 +581,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -650,9 +644,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 92f7fa355..0520e4548 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -14,7 +14,7 @@ - + -- cgit v1.2.3 From d56fa09ccc5e1a5f9440645330ce337273fa3bd7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 22 Jul 2014 12:36:34 -0400 Subject: stub out mock sync provider --- MediaBrowser.Api/Sync/SyncService.cs | 21 +++--- MediaBrowser.Controller/Sync/ISyncManager.cs | 20 +++--- MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs | 12 +++- MediaBrowser.Dlna/PlayTo/Device.cs | 14 ++-- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 1 - .../MediaBrowser.Model.Portable.csproj | 3 - .../MediaBrowser.Model.net35.csproj | 3 - MediaBrowser.Model/Dto/BaseItemDto.cs | 2 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 - MediaBrowser.Model/Querying/ItemFields.cs | 5 ++ MediaBrowser.Model/Sync/SyncJob.cs | 5 ++ MediaBrowser.Model/Sync/SyncJobRequest.cs | 12 +++- MediaBrowser.Model/Sync/SyncScheduleRequest.cs | 23 ------- .../Dto/DtoService.cs | 10 ++- .../HttpServer/HttpListenerHost.cs | 2 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Sync/AppSyncProvider.cs | 4 +- .../Sync/CloudSyncProvider.cs | 4 +- .../Sync/MockSyncProvider.cs | 33 ++++++++++ .../Sync/SyncManager.cs | 77 +++++++++++++++++++--- MediaBrowser.ServerApplication/ApplicationHost.cs | 8 +-- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 33 ++++------ 22 files changed, 193 insertions(+), 101 deletions(-) delete mode 100644 MediaBrowser.Model/Sync/SyncScheduleRequest.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index 57b5fa3f9..929d0a463 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using ServiceStack; +using System.Collections.Generic; using System.Threading.Tasks; namespace MediaBrowser.Api.Sync @@ -50,9 +51,11 @@ namespace MediaBrowser.Api.Sync { } - [Route("/Sync/Schedules", "POST", Summary = "Gets sync schedules.")] - public class CreateSyncSchedule : SyncScheduleRequest + [Route("/Sync/Targets", "GET", Summary = "Gets a list of available sync targets.")] + public class GetSyncTarget : IReturn> { + [ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } } [Authenticated] @@ -65,6 +68,13 @@ namespace MediaBrowser.Api.Sync _syncManager = syncManager; } + public object Get(GetSyncTarget request) + { + var result = _syncManager.GetSyncTargets(request.UserId); + + return ToOptimizedResult(result); + } + public object Get(GetSyncJobs request) { var result = _syncManager.GetJobs(new SyncJobQuery @@ -119,12 +129,5 @@ namespace MediaBrowser.Api.Sync Task.WaitAll(task); } - - public void Post(CreateSyncSchedule request) - { - var task = _syncManager.CreateSchedule(request); - - Task.WaitAll(task); - } } } diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index ef4b84f27..714e71a1f 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Querying; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using System.Collections.Generic; using System.Threading.Tasks; @@ -14,13 +15,6 @@ namespace MediaBrowser.Controller.Sync /// Task. Task> CreateJob(SyncJobRequest request); - /// - /// Creates the schedule. - /// - /// The request. - /// Task. - Task CreateSchedule(SyncScheduleRequest request); - /// /// Gets the jobs. /// @@ -69,7 +63,13 @@ namespace MediaBrowser.Controller.Sync /// /// Gets the synchronize targets. /// - /// IEnumerable<SyncTarget>. - IEnumerable GetSyncTargets(); + IEnumerable GetSyncTargets(string userId); + + /// + /// Supportses the synchronize. + /// + /// The item. + /// true if XXXX, false otherwise. + bool SupportsSync(BaseItem item); } } diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index 414bec715..230b5e145 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -86,9 +86,17 @@ namespace MediaBrowser.Dlna.Channels return; } - var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger).ConfigureAwait(false); + var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger) + .ConfigureAwait(false); - _servers.Add(device); + if (!_servers.Any(i => string.Equals(i.Properties.UUID, device.Properties.UUID, StringComparison.OrdinalIgnoreCase))) + { + _servers.Add(device); + } + } + catch (Exception ex) + { + } finally { diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 272e34a78..0d7c450d6 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -757,13 +757,17 @@ namespace MediaBrowser.Dlna.PlayTo var deviceProperties = new DeviceInfo(); + var friendlyNames = new List(); + var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault(); - if (name != null) - deviceProperties.Name = name.Value; + if (name != null && !string.IsNullOrWhiteSpace(name.Value)) + friendlyNames.Add(name.Value); + + var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); + if (room != null && !string.IsNullOrWhiteSpace(room.Value)) + friendlyNames.Add(room.Value); - var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); - if (name2 != null) - deviceProperties.Name = name2.Value; + deviceProperties.Name = string.Join(" ", friendlyNames.ToArray()); var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); if (model != null) diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index cea397cae..a5b816919 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Dlna.Ssdp; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; using System; diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 17463941c..a892f8bf1 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -866,9 +866,6 @@ Sync\SyncScheduleQuery.cs - - Sync\SyncScheduleRequest.cs - Sync\SyncTarget.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 9dafe0cef..ab2a99109 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -823,9 +823,6 @@ Sync\SyncScheduleQuery.cs - - Sync\SyncScheduleRequest.cs - Sync\SyncTarget.cs diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 7f7a4b8f0..d138ddf9b 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -53,6 +53,8 @@ namespace MediaBrowser.Model.Dto public bool IsUnidentified { get; set; } public int? AnimeSeriesIndex { get; set; } + + public bool? SupportsSync { get; set; } /// /// Gets or sets the DVD season number. diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 3c265ff91..6cdd2b8f5 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -303,7 +303,6 @@ - diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 790638446..9ceca311c 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -156,6 +156,11 @@ namespace MediaBrowser.Model.Querying /// Studios, + /// + /// The synchronize information + /// + SyncInfo, + /// /// The taglines of the item /// diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 34809834c..74dd79497 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -43,5 +43,10 @@ namespace MediaBrowser.Model.Sync /// /// The transcoded path. public string TranscodedPath { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } } } diff --git a/MediaBrowser.Model/Sync/SyncJobRequest.cs b/MediaBrowser.Model/Sync/SyncJobRequest.cs index 42b744db6..cd833068e 100644 --- a/MediaBrowser.Model/Sync/SyncJobRequest.cs +++ b/MediaBrowser.Model/Sync/SyncJobRequest.cs @@ -10,19 +10,25 @@ namespace MediaBrowser.Model.Sync /// The device identifier. public List TargetIds { get; set; } /// - /// Gets or sets the item identifier. + /// Gets or sets the item ids. /// - /// The item identifier. - public string ItemId { get; set; } + /// The item ids. + public List ItemIds { get; set; } /// /// Gets or sets the quality. /// /// The quality. public SyncQuality Quality { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } public SyncJobRequest() { TargetIds = new List(); + ItemIds = new List(); } } } diff --git a/MediaBrowser.Model/Sync/SyncScheduleRequest.cs b/MediaBrowser.Model/Sync/SyncScheduleRequest.cs deleted file mode 100644 index 076548f57..000000000 --- a/MediaBrowser.Model/Sync/SyncScheduleRequest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Model.Sync -{ - public class SyncScheduleRequest - { - /// - /// Gets or sets the device identifier. - /// - /// The device identifier. - public List TargetIds { get; set; } - /// - /// Gets or sets the quality. - /// - /// The quality. - public SyncQuality Quality { get; set; } - - public SyncScheduleRequest() - { - TargetIds = new List(); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index f01d973d6..c4499fac0 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -38,8 +39,9 @@ namespace MediaBrowser.Server.Implementations.Dto private readonly IProviderManager _providerManager; private readonly Func _channelManagerFactory; + private readonly ISyncManager _syncManager; - public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory) + public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory, ISyncManager syncManager) { _logger = logger; _libraryManager = libraryManager; @@ -50,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.Dto _fileSystem = fileSystem; _providerManager = providerManager; _channelManagerFactory = channelManagerFactory; + _syncManager = syncManager; } /// @@ -146,6 +149,11 @@ namespace MediaBrowser.Server.Implementations.Dto AttachBasicFields(dto, item, owner, fields); + if (fields.Contains(ItemFields.SyncInfo)) + { + dto.SupportsSync = _syncManager.SupportsSync(item); + } + if (fields.Contains(ItemFields.SoundtrackIds)) { var hasSoundtracks = item as IHasSoundtracks; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 94715b490..d0ea07056 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -158,7 +158,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); _listener = NativeWebSocket.IsSupported - ? _listener = new WebSocketSharpListener(_logger, _threadPoolManager) + ? _listener = new HttpListenerServer(_logger, _threadPoolManager) : _listener = new WebSocketSharpListener(_logger, _threadPoolManager); _listener.WebSocketHandler = WebSocketHandler; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 38e82db4f..9655771c6 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -272,6 +272,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs index 6dec2b717..c3cd047b6 100644 --- a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs @@ -10,12 +10,12 @@ namespace MediaBrowser.Server.Implementations.Sync { public IEnumerable GetSyncTargets() { - throw new NotImplementedException(); + return new List(); } public DeviceProfile GetDeviceProfile(SyncTarget target) { - throw new NotImplementedException(); + return new DeviceProfile(); } public string Name diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs index ec3ac1239..fd12b1f8a 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs @@ -19,12 +19,12 @@ namespace MediaBrowser.Server.Implementations.Sync public IEnumerable GetSyncTargets() { - throw new NotImplementedException(); + return new List(); } public DeviceProfile GetDeviceProfile(SyncTarget target) { - throw new NotImplementedException(); + return new DeviceProfile(); } public string Name diff --git a/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs new file mode 100644 index 000000000..bc079ad80 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class MockSyncProvider : ISyncProvider + { + public string Name + { + get { return "Dummy Sync"; } + } + + public IEnumerable GetSyncTargets() + { + return new List + { + new SyncTarget + { + Id = "mock".GetMD5().ToString("N"), + Name = "Mock Sync" + } + }; + } + + public DeviceProfile GetDeviceProfile(SyncTarget target) + { + return new DeviceProfile(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 508fc188d..50f1030f3 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -1,4 +1,7 @@ -using MediaBrowser.Controller.Sync; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using System; @@ -10,7 +13,7 @@ namespace MediaBrowser.Server.Implementations.Sync { public class SyncManager : ISyncManager { - private ISyncProvider[] _providers = new ISyncProvider[]{}; + private ISyncProvider[] _providers = new ISyncProvider[] { }; public void AddParts(IEnumerable providers) { @@ -22,11 +25,6 @@ namespace MediaBrowser.Server.Implementations.Sync throw new NotImplementedException(); } - public Task CreateSchedule(SyncScheduleRequest request) - { - throw new NotImplementedException(); - } - public QueryResult GetJobs(SyncJobQuery query) { throw new NotImplementedException(); @@ -57,9 +55,70 @@ namespace MediaBrowser.Server.Implementations.Sync throw new NotImplementedException(); } - public IEnumerable GetSyncTargets() + public IEnumerable GetSyncTargets(string userId) + { + return _providers + .SelectMany(GetSyncTargets) + .OrderBy(i => i.Name); + } + + private IEnumerable GetSyncTargets(ISyncProvider provider) + { + var providerId = GetSyncProviderId(provider); + + return provider.GetSyncTargets().Select(i => new SyncTarget + { + Name = i.Name, + Id = providerId + "-" + i.Id + }); + } + + private ISyncProvider GetSyncProvider(SyncTarget target) + { + var providerId = target.Id.Split(new[] { '-' }, 2).First(); + + return _providers.First(i => string.Equals(providerId, GetSyncProviderId(i))); + } + + private string GetSyncProviderId(ISyncProvider provider) + { + return (provider.GetType().Name + provider.Name).GetMD5().ToString("N"); + } + + public bool SupportsSync(BaseItem item) { - return _providers.SelectMany(i => i.GetSyncTargets()); + if (item.LocationType == LocationType.Virtual) + { + return false; + } + + if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + if (item.RunTimeTicks.HasValue) + { + var video = item as Video; + + if (video != null) + { + if (video.VideoType != VideoType.VideoFile) + { + return false; + } + + if (video.IsMultiPart) + { + return false; + } + } + + return true; + } + + return false; + } + + return false; } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 2208bc217..55843bbdb 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -561,7 +561,10 @@ namespace MediaBrowser.ServerApplication ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder); RegisterSingleInstance(ImageProcessor); - DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); + SyncManager = new SyncManager(); + RegisterSingleInstance(SyncManager); + + DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager); RegisterSingleInstance(DtoService); SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository); @@ -614,9 +617,6 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); - SyncManager = new SyncManager(); - RegisterSingleInstance(SyncManager); - var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 79b262fc3..f9a6fc059 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -43,37 +43,25 @@ namespace MediaBrowser.XbmcMetadata.Savers { var album = (MusicAlbum)item; - var tracks = album.Tracks - .ToList(); - - var artists = tracks - .SelectMany(i => - { - var list = new List(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase); - - foreach (var artist in artists) + foreach (var artist in album.Artists) { writer.WriteElementString("artist", artist); } - AddTracks(tracks, writer); + foreach (var artist in album.AlbumArtists) + { + writer.WriteElementString("albumartist", artist); + } + + AddTracks(album.Tracks, writer); } private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private void AddTracks(IEnumerable 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 be9f00a61..e21dc4a73 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 929d0a463..e0dc07cdc 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 dfe93c5c9..2d67ec975 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 de9b7e88a..27307a0e9 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 b891bcfb2..2bdbab084 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 af0ff8319..86699272e 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 5c3f10b54..aee118f9a 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 714e71a1f..1d5ab7d3e 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 000000000..9cce69bdc --- /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 230b5e145..fc5fd4061 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 000000000..0c62ada80 --- /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 333521bb5..29bd21407 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 ec8527738..461470b7a 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 0d7c450d6..85e18150e 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 29fd76aa5..ccb2acd1d 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 f40020c84..cd9b78201 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 3f84df462..d992e2026 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 8c4f6247c..c126af884 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 5d7762823..f69c261d8 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 681706321..83ef6e424 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 6ba1912a5..fb3a01bf2 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 a892f8bf1..c397cdcd5 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 ab2a99109..1b74fcf51 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 6cdd2b8f5..fd38ff723 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 74dd79497..f69fccae5 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 000000000..797318b2a --- /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 000000000..141546eb5 --- /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 f41544db9..74b35186e 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 cd833068e..7728aad9b 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 2a216fe04..ef4d8d7bf 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 297cbd145..000000000 --- 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 b704a358c..000000000 --- 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 cf7904e5c..e17a96a43 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 1b2e9fa6d..d7110c1ec 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 9e9936cab..33b69d71a 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 c0313561a..a2e1ba05a 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 c12f74fa6..78d1fac47 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 65fe32a99..32d53db43 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 08499a20b..34656f05b 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 d44811886..594ba9d23 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 d0ea07056..6e0b654fd 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 7f766129e..12106c32e 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 cf756d9f2..b18d0df5e 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 8229c8a2f..7b58dd7c4 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 64e0a6662..8310895e6 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 64d2db2e4..2ee3d49a4 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 4fa97fc9d..19a284d15 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 594277ef7..166465f72 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 10ee3586d..b483f7c42 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 f3e6fcdba..215cff22f 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 a756dc794..519d775de 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 dbe020908..43c9f3b9c 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 53047104c..d9855c52f 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 6bcb5aed5..5086dcfac 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 191a89000..37cff9888 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 51559053d..622ad2c05 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 2fa7f2ea7..80bdc43f7 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 05de4d65a..1f993f0d4 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 81922f713..667245839 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 a56ccf4de..bcca3aeb9 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 729768b78..71ca6fc49 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 c2fafba45..7a5bd7632 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 5ffff96dd..ea1b4a1bc 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 fe7451fed..2edf37326 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 5044e6d95..7ff88733a 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 dc4ac80cb..930401e5c 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 9c43a9c9e..94c6c9ec4 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 98a36f8dd..f1a323e6d 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 f5f3d9387..275cd20c3 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 43135ec81..51a27549e 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 e76d47a04..dba80e2b4 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 9a1847ea4..a3120ffe4 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 f474225fd..c558cbe8d 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 232e94a72..134ff44d8 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 c10e19102..165bf3f66 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 c1a871095..5862688a6 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 3032f71f0..135b640f2 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 a80d352b9..55d76aa8b 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 83e82070a..966365f7c 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 abdb98e97..c5ea589e8 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 3a49024f9..8b3a5e8c9 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 9988af0fa..c6c042640 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 98f108421..17748ecc7 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 6511dd7d0..352a031e1 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 9655771c6..ef6730b75 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 50f1030f3..a8d723ce3 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 000000000..bb22e992b --- /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 907500ae3..18739ddb3 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 55843bbdb..5c086c8cc 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 a93ed9196..2642ffde7 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 768e4ee5c..af24609e8 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 92487959d..9235beacf 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 25cff11cf..73174dacf 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 a45a44904..6071db6b5 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 d51c44ad4..c4edfb461 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 b23473295..210c743bf 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 c5319bb4ae9606e07d62525a022e5a67f85a7d43 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 4 Aug 2014 23:41:56 -0400 Subject: update playlist xml saving --- .../DefaultTheme/DefaultThemeService.cs | 673 --------------------- MediaBrowser.Api/DefaultTheme/Models.cs | 83 --- MediaBrowser.Api/Library/LibraryService.cs | 5 + MediaBrowser.Api/MediaBrowser.Api.csproj | 2 - .../Serialization/JsonSerializer.cs | 20 - .../Serialization/XmlSerializer.cs | 15 - .../Configuration/ConfigurationHelper.cs | 28 +- MediaBrowser.Controller/Entities/BaseItem.cs | 7 - MediaBrowser.Controller/Entities/LinkedChild.cs | 8 +- MediaBrowser.Controller/Library/TVUtils.cs | 2 - .../Providers/BaseItemXmlParser.cs | 23 +- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 2 +- .../MediaBrowser.LocalMetadata.csproj | 2 + .../Parsers/BoxSetXmlParser.cs | 9 +- .../Parsers/PlaylistXmlParser.cs | 72 +++ .../Providers/BoxSetXmlProvider.cs | 6 +- .../Providers/PlaylistXmlProvider.cs | 31 + .../Providers/TrailerXmlProvider.cs | 8 +- .../Savers/PlaylistXmlSaver.cs | 6 +- .../Savers/XmlSaverHelpers.cs | 6 +- .../Serialization/IJsonSerializer.cs | 8 - MediaBrowser.Model/Serialization/IXmlSerializer.cs | 7 - .../HttpServer/HttpListenerHost.cs | 2 +- .../Localization/JavaScript/ar.json | 15 +- .../Localization/JavaScript/ca.json | 15 +- .../Localization/JavaScript/cs.json | 15 +- .../Localization/JavaScript/da.json | 15 +- .../Localization/JavaScript/de.json | 15 +- .../Localization/JavaScript/el.json | 15 +- .../Localization/JavaScript/en_GB.json | 15 +- .../Localization/JavaScript/en_US.json | 15 +- .../Localization/JavaScript/es.json | 15 +- .../Localization/JavaScript/es_MX.json | 13 +- .../Localization/JavaScript/fr.json | 13 +- .../Localization/JavaScript/he.json | 15 +- .../Localization/JavaScript/it.json | 13 +- .../Localization/JavaScript/javascript.json | 7 +- .../Localization/JavaScript/kk.json | 15 +- .../Localization/JavaScript/ms.json | 15 +- .../Localization/JavaScript/nb.json | 15 +- .../Localization/JavaScript/nl.json | 13 +- .../Localization/JavaScript/pl.json | 15 +- .../Localization/JavaScript/pt_BR.json | 15 +- .../Localization/JavaScript/pt_PT.json | 15 +- .../Localization/JavaScript/ru.json | 19 +- .../Localization/JavaScript/sv.json | 13 +- .../Localization/JavaScript/tr.json | 19 +- .../Localization/JavaScript/vi.json | 15 +- .../Localization/JavaScript/zh_TW.json | 15 +- .../Localization/Server/ar.json | 48 +- .../Localization/Server/ca.json | 50 +- .../Localization/Server/cs.json | 48 +- .../Localization/Server/da.json | 48 +- .../Localization/Server/de.json | 48 +- .../Localization/Server/el.json | 48 +- .../Localization/Server/en_GB.json | 50 +- .../Localization/Server/en_US.json | 50 +- .../Localization/Server/es.json | 48 +- .../Localization/Server/es_MX.json | 48 +- .../Localization/Server/fr.json | 48 +- .../Localization/Server/he.json | 48 +- .../Localization/Server/it.json | 50 +- .../Localization/Server/kk.json | 52 +- .../Localization/Server/ko.json | 50 +- .../Localization/Server/ms.json | 50 +- .../Localization/Server/nb.json | 48 +- .../Localization/Server/nl.json | 50 +- .../Localization/Server/pl.json | 48 +- .../Localization/Server/pt_BR.json | 50 +- .../Localization/Server/pt_PT.json | 48 +- .../Localization/Server/ru.json | 58 +- .../Localization/Server/server.json | 47 +- .../Localization/Server/sv.json | 48 +- .../Localization/Server/tr.json | 66 +- .../Localization/Server/vi.json | 48 +- .../Localization/Server/zh_TW.json | 48 +- .../Persistence/SqliteExtensions.cs | 22 + 77 files changed, 1768 insertions(+), 1002 deletions(-) delete mode 100644 MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs delete mode 100644 MediaBrowser.Api/DefaultTheme/Models.cs create mode 100644 MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs create mode 100644 MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs (limited to 'MediaBrowser.Server.Implementations/HttpServer') diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs deleted file mode 100644 index 21ba47bd4..000000000 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ /dev/null @@ -1,673 +0,0 @@ -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -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.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Api.DefaultTheme -{ - [Route("/MBT/DefaultTheme/Games", "GET")] - public class GetGamesView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "RecentlyPlayedGamesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int RecentlyPlayedGamesLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/TV", "GET")] - public class GetTvView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ComedyGenre { get; set; } - - [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string RomanceGenre { get; set; } - - [ApiMember(Name = "TopCommunityRating", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double TopCommunityRating { get; set; } - - [ApiMember(Name = "NextUpEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int NextUpEpisodeLimit { get; set; } - - [ApiMember(Name = "ResumableEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int ResumableEpisodeLimit { get; set; } - - [ApiMember(Name = "LatestEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestEpisodeLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/Movies", "GET")] - public class GetMovieView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "FamilyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string FamilyGenre { get; set; } - - [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ComedyGenre { get; set; } - - [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string RomanceGenre { get; set; } - - [ApiMember(Name = "LatestMoviesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestMoviesLimit { get; set; } - - [ApiMember(Name = "LatestTrailersLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestTrailersLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/Favorites", "GET")] - public class GetFavoritesView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Authenticated] - public class DefaultThemeService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IDtoService _dtoService; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IUserDataManager _userDataManager; - - private readonly IImageProcessor _imageProcessor; - private readonly IItemRepository _itemRepo; - - public DefaultThemeService(IUserManager userManager, IDtoService dtoService, ILogger logger, ILibraryManager libraryManager, IImageProcessor imageProcessor, IUserDataManager userDataManager, IItemRepository itemRepo) - { - _userManager = userManager; - _dtoService = dtoService; - _logger = logger; - _libraryManager = libraryManager; - _imageProcessor = imageProcessor; - _userDataManager = userDataManager; - _itemRepo = itemRepo; - } - - public object Get(GetFavoritesView request) - { - var user = _userManager.GetUserById(request.UserId); - - var allItems = user.RootFolder.GetRecursiveChildren(user) - .ToList(); - - var allFavoriteItems = allItems.Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite) - .ToList(); - - var itemsWithImages = allFavoriteItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)) - .ToList(); - - var itemsWithBackdrops = allFavoriteItems.Where(i => i.GetImages(ImageType.Backdrop).Any()) - .ToList(); - - var view = new FavoritesView(); - - var fields = new List(); - - view.BackdropItems = FilterItemsForBackdropDisplay(itemsWithBackdrops) - .Randomize("backdrop") - .Take(10) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - var spotlightItems = itemsWithBackdrops.Randomize("spotlight") - .Take(10) - .ToList(); - - view.SpotlightItems = spotlightItems - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - fields.Add(ItemFields.PrimaryImageAspectRatio); - - view.Albums = itemsWithImages - .OfType() - .Randomize() - .Take(4) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Books = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Episodes = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Games = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Movies = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Series = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Songs = itemsWithImages - .OfType