浏览代码

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser into upstream-master

Tim Hobbs 11 年之前
父节点
当前提交
6e10825631
共有 100 个文件被更改,包括 1793 次插入1646 次删除
  1. 1 23
      MediaBrowser.Api/ItemLookupService.cs
  2. 1 36
      MediaBrowser.Api/Library/LibraryService.cs
  3. 162 0
      MediaBrowser.Api/Library/SubtitleService.cs
  4. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  5. 53 22
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  6. 2 2
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  7. 2 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  8. 2 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  9. 2 2
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  10. 3 3
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  11. 3 2
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  12. 5 2
      MediaBrowser.Api/Playback/StreamState.cs
  13. 13 1
      MediaBrowser.Api/SessionsService.cs
  14. 1 1
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  15. 16 11
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  16. 11 8
      MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
  17. 2 0
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  18. 5 0
      MediaBrowser.Common/Net/IWebSocket.cs
  19. 5 0
      MediaBrowser.Common/Net/IWebSocketConnection.cs
  20. 1 1
      MediaBrowser.Controller/Channels/Channel.cs
  21. 24 2
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  22. 8 1
      MediaBrowser.Controller/Channels/ChannelCategoryItem.cs
  23. 2 0
      MediaBrowser.Controller/Channels/ChannelItemInfo.cs
  24. 3 0
      MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
  25. 21 0
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  26. 17 0
      MediaBrowser.Controller/Channels/IChannel.cs
  27. 1 1
      MediaBrowser.Controller/Channels/IChannelItem.cs
  28. 8 0
      MediaBrowser.Controller/Channels/IChannelManager.cs
  29. 5 1
      MediaBrowser.Controller/Channels/IChannelMediaItem.cs
  30. 3 1
      MediaBrowser.Controller/Entities/AdultVideo.cs
  31. 3 1
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  32. 7 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  33. 4 1
      MediaBrowser.Controller/Entities/Folder.cs
  34. 3 1
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  35. 7 1
      MediaBrowser.Controller/Entities/MusicVideo.cs
  36. 3 1
      MediaBrowser.Controller/Entities/Trailer.cs
  37. 49 35
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  38. 3 0
      MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
  39. 11 6
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  40. 9 1
      MediaBrowser.Controller/Session/ISessionController.cs
  41. 13 0
      MediaBrowser.Controller/Session/SessionInfo.cs
  42. 23 2
      MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
  43. 1 0
      MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
  44. 3 0
      MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
  45. 1 1
      MediaBrowser.Dlna/DlnaManager.cs
  46. 7 1
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  47. 3 1
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  48. 22 22
      MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs
  49. 6 1
      MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
  50. 2 2
      MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs
  51. 0 91
      MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
  52. 0 114
      MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
  53. 0 168
      MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
  54. 0 235
      MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs
  55. 0 95
      MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
  56. 0 311
      MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs
  57. 1 1
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  58. 0 5
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  59. 1 0
      MediaBrowser.Model/Configuration/LiveTvOptions.cs
  60. 5 2
      MediaBrowser.Model/Configuration/NotificationType.cs
  61. 0 39
      MediaBrowser.Model/Dlna/DeviceProfile.cs
  62. 6 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  63. 1 2
      MediaBrowser.Model/Entities/LibraryUpdateInfo.cs
  64. 6 0
      MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
  65. 1 0
      MediaBrowser.Model/Querying/ItemSortBy.cs
  66. 4 0
      MediaBrowser.Model/Session/SessionCapabilities.cs
  67. 7 1
      MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs
  68. 1 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  69. 2 2
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  70. 17 112
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  71. 5 2
      MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
  72. 135 0
      MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
  73. 12 0
      MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
  74. 12 4
      MediaBrowser.Providers/Movies/MovieXmlParser.cs
  75. 7 1
      MediaBrowser.Providers/Movies/MovieXmlProvider.cs
  76. 7 1
      MediaBrowser.Providers/Movies/TrailerXmlProvider.cs
  77. 36 14
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  78. 138 10
      MediaBrowser.Providers/Subtitles/SubtitleManager.cs
  79. 8 2
      MediaBrowser.Providers/TV/EpisodeXmlParser.cs
  80. 4 1
      MediaBrowser.Providers/TV/EpisodeXmlProvider.cs
  81. 57 15
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  82. 8 0
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  83. 1 1
      MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  84. 50 8
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
  85. 5 1
      MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
  86. 9 1
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  87. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/ar.json
  88. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/ca.json
  89. 47 13
      MediaBrowser.Server.Implementations/Localization/Server/cs.json
  90. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/da.json
  91. 75 41
      MediaBrowser.Server.Implementations/Localization/Server/de.json
  92. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/el.json
  93. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/en_GB.json
  94. 49 15
      MediaBrowser.Server.Implementations/Localization/Server/en_US.json
  95. 39 5
      MediaBrowser.Server.Implementations/Localization/Server/es.json
  96. 51 17
      MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
  97. 47 13
      MediaBrowser.Server.Implementations/Localization/Server/fr.json
  98. 39 5
      MediaBrowser.Server.Implementations/Localization/Server/he.json
  99. 53 19
      MediaBrowser.Server.Implementations/Localization/Server/it.json
  100. 39 5
      MediaBrowser.Server.Implementations/Localization/Server/kk.json

+ 1 - 23
MediaBrowser.Api/ItemLookupService.cs

@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using ServiceStack;
@@ -32,16 +31,6 @@ namespace MediaBrowser.Api
         public string Id { get; set; }
     }
 
-    [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
-    public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
-    {
-        [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/RemoteSearch/Movie", "POST")]
     [Api(Description = "Gets external id infos for an item")]
     public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
@@ -121,24 +110,13 @@ namespace MediaBrowser.Api
         private readonly IServerApplicationPaths _appPaths;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryManager _libraryManager;
-        private readonly ISubtitleManager _subtitleManager;
 
-        public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager, ISubtitleManager subtitleManager)
+        public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager)
         {
             _providerManager = providerManager;
             _appPaths = appPaths;
             _fileSystem = fileSystem;
             _libraryManager = libraryManager;
-            _subtitleManager = subtitleManager;
-        }
-
-        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(GetExternalIdInfos request)

+ 1 - 36
MediaBrowser.Api/Library/LibraryService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -35,21 +34,6 @@ namespace MediaBrowser.Api.Library
         public string Id { get; set; }
     }
 
-    [Route("/Videos/{Id}/Subtitles/{Index}", "GET")]
-    [Api(Description = "Gets an external subtitle file")]
-    public class GetSubtitle
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
-        public int Index { get; set; }
-    }
-
     /// <summary>
     /// Class GetCriticReviews
     /// </summary>
@@ -305,25 +289,6 @@ namespace MediaBrowser.Api.Library
             return ToStaticFileResult(item.Path);
         }
 
-        public object Get(GetSubtitle request)
-        {
-            var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
-            {
-
-                Index = request.Index,
-                ItemId = new Guid(request.Id),
-                Type = MediaStreamType.Subtitle
-
-            }).FirstOrDefault();
-
-            if (subtitleStream == null)
-            {
-                throw new ResourceNotFoundException();
-            }
-
-            return ToStaticFileResult(subtitleStream.Path);
-        }
-
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 162 - 0
MediaBrowser.Api/Library/SubtitleService.cs

@@ -0,0 +1,162 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Library
+{
+    [Route("/Videos/{Id}/Subtitles/{Index}", "GET", Summary = "Gets an external subtitle file")]
+    public class GetSubtitle
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+        public int Index { get; set; }
+    }
+
+    [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
+    public class DeleteSubtitle
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [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<List<RemoteSubtitleInfo>>
+    {
+        [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<List<SubtitleProviderInfo>>
+    {
+        [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; }
+    }
+
+    public class SubtitleService : BaseApiService
+    {
+        private readonly ILibraryManager _libraryManager;
+        private readonly ISubtitleManager _subtitleManager;
+        private readonly IItemRepository _itemRepo;
+
+        public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, IItemRepository itemRepo)
+        {
+            _libraryManager = libraryManager;
+            _subtitleManager = subtitleManager;
+            _itemRepo = itemRepo;
+        }
+
+        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)
+        {
+            var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+            {
+
+                Index = request.Index,
+                ItemId = new Guid(request.Id),
+                Type = MediaStreamType.Subtitle
+
+            }).FirstOrDefault();
+
+            if (subtitleStream == null)
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            return ToStaticFileResult(subtitleStream.Path);
+        }
+
+        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, 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);
+                }
+
+            });
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -68,6 +68,7 @@
     <Compile Include="ChannelService.cs" />
     <Compile Include="Dlna\DlnaServerService.cs" />
     <Compile Include="Dlna\DlnaService.cs" />
+    <Compile Include="Library\SubtitleService.cs" />
     <Compile Include="Movies\CollectionService.cs" />
     <Compile Include="Music\AlbumsService.cs" />
     <Compile Include="AppThemeService.cs" />

+ 53 - 22
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dto;
@@ -25,7 +26,6 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Api.Playback
 {
@@ -71,6 +71,7 @@ namespace MediaBrowser.Api.Playback
         protected IItemRepository ItemRepository { get; private set; }
         protected ILiveTvManager LiveTvManager { get; private set; }
         protected IDlnaManager DlnaManager { get; private set; }
+        protected IChannelManager ChannelManager { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
@@ -83,8 +84,9 @@ namespace MediaBrowser.Api.Playback
         /// <param name="dtoService">The dto service.</param>
         /// <param name="fileSystem">The file system.</param>
         /// <param name="itemRepository">The item repository.</param>
-        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager)
+        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager)
         {
+            ChannelManager = channelManager;
             DlnaManager = dlnaManager;
             EncodingManager = encodingManager;
             LiveTvManager = liveTvManager;
@@ -169,13 +171,28 @@ namespace MediaBrowser.Api.Playback
         /// <returns>System.String.</returns>
         protected virtual string GetMapArgs(StreamState state)
         {
-            var args = string.Empty;
+            // If we don't have known media info
+            // If input is video, use -sn to drop subtitles
+            // Otherwise just return empty
+            if (state.VideoStream == null && state.AudioStream == null)
+            {
+                return state.IsInputVideo ? "-sn" : string.Empty;
+            }
+
+            // We have media info, but we don't know the stream indexes
+            if (state.VideoStream != null && state.VideoStream.Index == -1)
+            {
+                return "-sn";
+            }
 
-            if (!state.HasMediaStreams)
+            // We have media info, but we don't know the stream indexes
+            if (state.AudioStream != null && state.AudioStream.Index == -1)
             {
                 return state.IsInputVideo ? "-sn" : string.Empty;
             }
 
+            var args = string.Empty;
+
             if (state.VideoStream != null)
             {
                 args += string.Format("-map 0:{0}", state.VideoStream.Index);
@@ -350,11 +367,11 @@ namespace MediaBrowser.Api.Playback
                 switch (qualitySetting)
                 {
                     case EncodingQuality.HighSpeed:
-                        crf = "16";
+                        crf = "14";
                         profileScore = 2;
                         break;
                     case EncodingQuality.HighQuality:
-                        crf = "10";
+                        crf = "8";
                         profileScore = 1;
                         break;
                     case EncodingQuality.MaxQuality:
@@ -1329,13 +1346,14 @@ namespace MediaBrowser.Api.Playback
                 throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
             }
 
+            List<MediaStream> mediaStreams = null;
+
             if (item is ILiveTvRecording)
             {
                 var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
 
                 state.VideoType = VideoType.VideoFile;
                 state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-                state.PlayableStreamFileNames = new List<string>();
 
                 var path = recording.RecordingInfo.Path;
                 var mediaUrl = recording.RecordingInfo.Url;
@@ -1345,6 +1363,7 @@ namespace MediaBrowser.Api.Playback
                     var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false);
 
                     state.LiveTvStreamId = streamInfo.Id;
+                    mediaStreams = streamInfo.MediaStreams;
 
                     path = streamInfo.Path;
                     mediaUrl = streamInfo.Url;
@@ -1381,11 +1400,11 @@ namespace MediaBrowser.Api.Playback
 
                 state.VideoType = VideoType.VideoFile;
                 state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-                state.PlayableStreamFileNames = new List<string>();
 
                 var streamInfo = await LiveTvManager.GetChannelStream(request.Id, cancellationToken).ConfigureAwait(false);
 
                 state.LiveTvStreamId = streamInfo.Id;
+                mediaStreams = streamInfo.MediaStreams;
 
                 if (!string.IsNullOrEmpty(streamInfo.Path))
                 {
@@ -1406,6 +1425,16 @@ namespace MediaBrowser.Api.Playback
                 state.InputVideoSync = "-1";
                 state.InputAudioSync = "1";
             }
+            else if (item is IChannelMediaItem)
+            {
+                var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(request.Id, CancellationToken.None).ConfigureAwait(false);
+
+                var source = channelMediaSources.First();
+                state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+                state.IsRemote = source.IsRemote;
+                state.MediaPath = source.Path;
+                state.RunTimeTicks = item.RunTimeTicks;
+            }
             else
             {
                 state.MediaPath = item.Path;
@@ -1424,7 +1453,11 @@ namespace MediaBrowser.Api.Playback
                         : video.PlayableStreamFileNames.ToList();
 
                     state.DeInterlace = string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase);
-                    state.InputTimestamp = video.Timestamp ?? TransportStreamTimestamp.None;
+
+                    if (video.Timestamp.HasValue)
+                    {
+                        state.InputTimestamp = video.Timestamp.Value;
+                    }
 
                     state.InputContainer = video.Container;
                 }
@@ -1440,7 +1473,7 @@ namespace MediaBrowser.Api.Playback
 
             var videoRequest = request as VideoStreamRequest;
 
-            var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery
+            mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery
             {
                 ItemId = item.Id
 
@@ -1469,8 +1502,6 @@ namespace MediaBrowser.Api.Playback
                 state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
             }
 
-            state.HasMediaStreams = mediaStreams.Count > 0;
-
             state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
             state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
 
@@ -1504,16 +1535,6 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
-            var headers = new Dictionary<string, string>();
-            foreach (var key in Request.Headers.AllKeys)
-            {
-                headers[key] = Request.Headers[key];
-            }
-
-            state.DeviceProfile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
-                DlnaManager.GetProfile(headers) :
-                DlnaManager.GetProfile(state.Request.DeviceProfileId);
-
             return state;
         }
 
@@ -1638,6 +1659,16 @@ namespace MediaBrowser.Api.Playback
 
         private void ApplyDeviceProfileSettings(StreamState state)
         {
+            var headers = new Dictionary<string, string>();
+            foreach (var key in Request.Headers.AllKeys)
+            {
+                headers[key] = Request.Headers[key];
+            }
+
+            state.DeviceProfile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
+                DlnaManager.GetProfile(headers) :
+                DlnaManager.GetProfile(state.Request.DeviceProfileId);
+
             var profile = state.DeviceProfile;
 
             if (profile == null)

+ 2 - 2
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dto;
@@ -24,8 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public abstract class BaseHlsService : BaseStreamingService
     {
-        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager)
-            : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
+        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
         {
         }
 

+ 2 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dto;
@@ -59,7 +60,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     public class DynamicHlsService : BaseHlsService
     {
-        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
+        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
         {
         }
 

+ 2 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dto;
@@ -53,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public class VideoHlsService : BaseHlsService
     {
-        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
+        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
         {
         }
 

+ 2 - 2
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -43,8 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     {
-        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor)
-            : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor)
+        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
         {
         }
 

+ 3 - 3
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -26,11 +27,10 @@ namespace MediaBrowser.Api.Playback.Progressive
         protected readonly IImageProcessor ImageProcessor;
         protected readonly IHttpClient HttpClient;
 
-        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor)
-            : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
+        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
         {
-            HttpClient = httpClient;
             ImageProcessor = imageProcessor;
+            HttpClient = httpClient;
         }
 
         /// <summary>

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -59,7 +60,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     {
-        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor)
+        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
         {
         }
 
@@ -183,7 +184,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         private string GetAudioArguments(StreamState state)
         {
             // If the video doesn't have an audio stream, return a default.
-            if (state.AudioStream == null && state.HasMediaStreams)
+            if (state.AudioStream == null && state.VideoStream != null)
             {
                 return string.Empty;
             }

+ 5 - 2
MediaBrowser.Api/Playback/StreamState.cs

@@ -28,6 +28,11 @@ namespace MediaBrowser.Api.Playback
             get { return Request as VideoStreamRequest; }
         }
 
+        public StreamState()
+        {
+            PlayableStreamFileNames = new List<string>();
+        }
+
         /// <summary>
         /// Gets or sets the log file stream.
         /// </summary>
@@ -57,8 +62,6 @@ namespace MediaBrowser.Api.Playback
 
         public List<string> PlayableStreamFileNames { get; set; }
 
-        public bool HasMediaStreams { get; set; }
-
         public string LiveTvStreamId { get; set; }
 
         public int SegmentLength = 10;

+ 13 - 1
MediaBrowser.Api/SessionsService.cs

@@ -217,6 +217,12 @@ namespace MediaBrowser.Api
 
         [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string SupportedCommands { get; set; }
+
+        [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MessageCallbackUrl { get; set; }
+
+        [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
+        public bool SupportsMediaControl { get; set; }
     }
 
     /// <summary>
@@ -258,6 +264,8 @@ namespace MediaBrowser.Api
 
             if (request.ControllableByUserId.HasValue)
             {
+                result = result.Where(i => i.SupportsMediaControl);
+
                 var user = _userManager.GetUserById(request.ControllableByUserId.Value);
 
                 if (!user.Configuration.EnableRemoteControlOfOtherUsers)
@@ -407,7 +415,11 @@ namespace MediaBrowser.Api
             {
                 PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
 
-                SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
+                SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+
+                SupportsMediaControl = request.SupportsMediaControl,
+
+                MessageCallbackUrl = request.MessageCallbackUrl
             });
         }
 

+ 1 - 1
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -390,7 +390,7 @@ namespace MediaBrowser.Common.Implementations
                 FileSystemManager = CreateFileSystemManager();
                 RegisterSingleInstance(FileSystemManager);
 
-                HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager);
+                HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager, ConfigurationManager);
                 RegisterSingleInstance(HttpClient);
 
                 NetworkManager = CreateNetworkManager();

+ 16 - 11
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -1,5 +1,4 @@
-using System.Collections.Specialized;
-using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
@@ -7,6 +6,7 @@ using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Globalization;
 using System.IO;
 using System.Linq;
@@ -41,6 +41,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         private readonly IApplicationPaths _appPaths;
 
         private readonly IFileSystem _fileSystem;
+        private readonly IConfigurationManager _config;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -51,7 +52,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         /// <exception cref="System.ArgumentNullException">appPaths
         /// or
         /// logger</exception>
-        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
+        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IConfigurationManager config)
         {
             if (appPaths == null)
             {
@@ -64,6 +65,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             _logger = logger;
             _fileSystem = fileSystem;
+            _config = config;
             _appPaths = appPaths;
 
             // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
@@ -116,8 +118,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
 
-            request.ConnectionGroupName = GetHostFromUrl(options.Url);
-            request.KeepAlive = true;
+            request.KeepAlive = options.EnableKeepAlive;
             request.Method = method;
             request.Pipelined = true;
             request.Timeout = 20000;
@@ -128,14 +129,18 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             }
 
 #if !__MonoCS__
-            // This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
-            // May need to remove this for mono
-            var sp = request.ServicePoint;
-            if (_httpBehaviorPropertyInfo == null)
+            if (options.EnableKeepAlive)
             {
-                _httpBehaviorPropertyInfo = sp.GetType().GetProperty("HttpBehaviour", BindingFlags.Instance | BindingFlags.NonPublic);
+                // This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
+                // May need to remove this for mono
+                var sp = request.ServicePoint;
+                if (_httpBehaviorPropertyInfo == null)
+                {
+                    _httpBehaviorPropertyInfo = sp.GetType().GetProperty("HttpBehaviour", BindingFlags.Instance | BindingFlags.NonPublic);
+                }
+
+                _httpBehaviorPropertyInfo.SetValue(sp, (byte)0, null);
             }
-            _httpBehaviorPropertyInfo.SetValue(sp, (byte)0, null);
 #endif
 
             return request;

+ 11 - 8
MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs

@@ -351,18 +351,21 @@ namespace MediaBrowser.Common.Implementations.IO
                 throw new ArgumentNullException("to");
             }
 
-            path = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
+            var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
 
-            if (to.IndexOf('/') != -1)
+            if (!string.Equals(newPath, path))
             {
-                path = path.Replace('\\', '/');
-            }
-            else
-            {
-                path = path.Replace('/', '\\');
+                if (to.IndexOf('/') != -1)
+                {
+                    newPath = path.Replace('\\', '/');
+                }
+                else
+                {
+                    newPath = path.Replace('/', '\\');
+                }
             }
 
-            return path;
+            return newPath;
         }
     }
 }

+ 2 - 0
MediaBrowser.Common/Net/HttpRequestOptions.cs

@@ -82,6 +82,7 @@ namespace MediaBrowser.Common.Net
         public bool LogRequest { get; set; }
 
         public bool LogErrorResponseBody { get; set; }
+        public bool EnableKeepAlive { get; set; }
 
         private string GetHeaderValue(string name)
         {
@@ -99,6 +100,7 @@ namespace MediaBrowser.Common.Net
         {
             EnableHttpCompression = true;
             BufferContent = true;
+            EnableKeepAlive = true;
 
             RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 

+ 5 - 0
MediaBrowser.Common/Net/IWebSocket.cs

@@ -10,6 +10,11 @@ namespace MediaBrowser.Common.Net
     /// </summary>
     public interface IWebSocket : IDisposable
     {
+        /// <summary>
+        /// Occurs when [closed].
+        /// </summary>
+        event EventHandler<EventArgs> Closed;
+
         /// <summary>
         /// Gets or sets the state.
         /// </summary>

+ 5 - 0
MediaBrowser.Common/Net/IWebSocketConnection.cs

@@ -7,6 +7,11 @@ namespace MediaBrowser.Common.Net
 {
     public interface IWebSocketConnection : IDisposable
     {
+        /// <summary>
+        /// Occurs when [closed].
+        /// </summary>
+        event EventHandler<EventArgs> Closed;
+        
         /// <summary>
         /// Gets the id.
         /// </summary>

+ 1 - 1
MediaBrowser.Controller/Channels/Channel.cs

@@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Channels
 
         public override bool IsVisible(User user)
         {
-            if (user.Configuration.BlockedChannels.Contains(Name, StringComparer.OrdinalIgnoreCase))
+            if (user.Configuration.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
             {
                 return false;
             }

+ 24 - 2
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -1,6 +1,8 @@
-using System.Linq;
-using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -18,6 +20,8 @@ namespace MediaBrowser.Controller.Channels
 
         public string OriginalImageUrl { get; set; }
 
+        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
+        
         protected override bool GetBlockUnratedValue(UserConfiguration config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
@@ -30,5 +34,23 @@ namespace MediaBrowser.Controller.Channels
                 return false;
             }
         }
+
+        public ChannelAudioItem()
+        {
+            ChannelMediaSources = new List<ChannelMediaInfo>();
+        }
+
+        public override LocationType LocationType
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(Path))
+                {
+                    return LocationType.Remote;
+                }
+                
+                return base.LocationType;
+            }
+        }
     }
 }

+ 8 - 1
MediaBrowser.Controller/Channels/ChannelCategoryItem.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -8,10 +9,11 @@ namespace MediaBrowser.Controller.Channels
         public string ExternalId { get; set; }
 
         public string ChannelId { get; set; }
-        
+
         public ChannelItemType ChannelItemType { get; set; }
 
         public string OriginalImageUrl { get; set; }
+        public List<string> Tags { get; set; }
 
         protected override bool GetBlockUnratedValue(UserConfiguration config)
         {
@@ -26,5 +28,10 @@ namespace MediaBrowser.Controller.Channels
                 return false;
             }
         }
+
+        public ChannelCategoryItem()
+        {
+            Tags = new List<string>();
+        }
     }
 }

+ 2 - 0
MediaBrowser.Controller/Channels/ChannelItemInfo.cs

@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Channels
 
         public List<string> Genres { get; set; }
         public List<string> Studios { get; set; }
+        public List<string> Tags { get; set; }
 
         public List<PersonInfo> People { get; set; }
         
@@ -49,6 +50,7 @@ namespace MediaBrowser.Controller.Channels
             Genres = new List<string>();
             Studios = new List<string>();
             People = new List<PersonInfo>();
+            Tags = new List<string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
     }

+ 3 - 0
MediaBrowser.Controller/Channels/ChannelMediaInfo.cs

@@ -18,9 +18,12 @@ namespace MediaBrowser.Controller.Channels
         public int? Height { get; set; }
         public int? AudioChannels { get; set; }
 
+        public bool IsRemote { get; set; }
+
         public ChannelMediaInfo()
         {
             RequiredHttpHeaders = new Dictionary<string, string>();
+            IsRemote = true;
         }
     }
 }

+ 21 - 0
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 
@@ -20,6 +21,8 @@ namespace MediaBrowser.Controller.Channels
 
         public string OriginalImageUrl { get; set; }
 
+        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
+        
         public override string GetUserDataKey()
         {
             if (ContentType == ChannelMediaContentType.Trailer)
@@ -55,5 +58,23 @@ namespace MediaBrowser.Controller.Channels
                 return false;
             }
         }
+
+        public ChannelVideoItem()
+        {
+            ChannelMediaSources = new List<ChannelMediaInfo>();
+        }
+
+        public override LocationType LocationType
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(Path))
+                {
+                    return LocationType.Remote;
+                }
+
+                return base.LocationType;
+            }
+        }
     }
 }

+ 17 - 0
MediaBrowser.Controller/Channels/IChannel.cs

@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Channels
         /// <value>The name.</value>
         string Name { get; }
 
+        /// <summary>
+        /// Gets the data version.
+        /// </summary>
+        /// <value>The data version.</value>
+        string DataVersion { get; }
+
         /// <summary>
         /// Gets the channel information.
         /// </summary>
@@ -59,4 +65,15 @@ namespace MediaBrowser.Controller.Channels
         /// <returns>IEnumerable{ImageType}.</returns>
         IEnumerable<ImageType> GetSupportedChannelImages();
     }
+
+    public interface IRequiresMediaInfoCallback
+    {
+        /// <summary>
+        /// Gets the channel item media information.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{ChannelMediaInfo}}.</returns>
+        Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken);
+    }
 }

+ 1 - 1
MediaBrowser.Controller/Channels/IChannelItem.cs

@@ -2,7 +2,7 @@
 
 namespace MediaBrowser.Controller.Channels
 {
-    public interface IChannelItem : IHasImages
+    public interface IChannelItem : IHasImages, IHasTags
     {
         string ChannelId { get; set; }
 

+ 8 - 0
MediaBrowser.Controller/Channels/IChannelManager.cs

@@ -31,5 +31,13 @@ namespace MediaBrowser.Controller.Channels
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
         Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the channel item media sources.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{ChannelMediaInfo}}.</returns>
+        Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken);
     }
 }

+ 5 - 1
MediaBrowser.Controller/Channels/IChannelMediaItem.cs

@@ -1,9 +1,13 @@
-namespace MediaBrowser.Controller.Channels
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Channels
 {
     public interface IChannelMediaItem : IChannelItem
     {
         bool IsInfiniteStream { get; set; }
 
         ChannelMediaContentType ContentType { get; set; }
+
+        List<ChannelMediaInfo> ChannelMediaSources { get; set; }
     }
 }

+ 3 - 1
MediaBrowser.Controller/Entities/AdultVideo.cs

@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Providers;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class AdultVideo : Video, IHasPreferredMetadataLanguage, IHasTaglines
+    public class AdultVideo : Video, IHasProductionLocations, IHasPreferredMetadataLanguage, IHasTaglines
     {
         /// <summary>
         /// Gets or sets the preferred metadata language.
@@ -16,12 +16,14 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The preferred metadata country code.</value>
         public string PreferredMetadataCountryCode { get; set; }
+        public List<string> ProductionLocations { get; set; }
 
         public List<string> Taglines { get; set; }
 
         public AdultVideo()
         {
             Taglines = new List<string>();
+            ProductionLocations = new List<string>();
         }
 
         public override bool BeforeMetadataRefresh()

+ 3 - 1
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -10,16 +10,18 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// <summary>
     /// Class Audio
     /// </summary>
-    public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>
+    public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>, IHasTags
     {
         public string FormatName { get; set; }
         public long? Size { get; set; }
         public string Container { get; set; }
         public int? TotalBitrate { get; set; }
+        public List<string> Tags { get; set; }
 
         public Audio()
         {
             Artists = new List<string>();
+            Tags = new List<string>();
         }
 
         /// <summary>

+ 7 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1501,6 +1501,13 @@ namespace MediaBrowser.Controller.Entities
             return userdata != null && userdata.Played;
         }
 
+        public bool IsFavoriteOrLiked(User user)
+        {
+            var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey());
+
+            return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false));
+        }
+
         public virtual bool IsUnplayed(User user)
         {
             var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey());

+ 4 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -281,7 +281,10 @@ namespace MediaBrowser.Controller.Entities
         {
             if (this is ICollectionFolder)
             {
-                if (user.Configuration.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
+                if (user.Configuration.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
+
+                    // Backwards compatibility
+                    user.Configuration.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
                 {
                     return false;
                 }

+ 3 - 1
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities.Movies
     /// <summary>
     /// Class Movie
     /// </summary>
-    public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
+    public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
     {
         public List<Guid> SpecialFeatureIds { get; set; }
 
@@ -22,6 +22,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
+        public List<string> ProductionLocations { get; set; }
 
         /// <summary>
         /// This is just a cache to enable quick access by Id
@@ -48,6 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
             BoxSetIdList = new List<Guid>();
             Taglines = new List<string>();
             Keywords = new List<string>();
+            ProductionLocations = new List<string>();
         }
 
         public string AwardSummary { get; set; }

+ 7 - 1
MediaBrowser.Controller/Entities/MusicVideo.cs

@@ -9,7 +9,7 @@ using System.Runtime.Serialization;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget, IHasLookupInfo<MusicVideoInfo>
+    public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasProductionLocations, IHasBudget, IHasLookupInfo<MusicVideoInfo>
     {
         /// <summary>
         /// Gets or sets the artist.
@@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The revenue.</value>
         public double? Revenue { get; set; }
+        public List<string> ProductionLocations { get; set; }
+
+        public MusicVideo()
+        {
+            ProductionLocations = new List<string>();
+        }
 
         [IgnoreDataMember]
         public List<string> AllArtists

+ 3 - 1
MediaBrowser.Controller/Entities/Trailer.cs

@@ -12,11 +12,12 @@ namespace MediaBrowser.Controller.Entities
     /// <summary>
     /// Class Trailer
     /// </summary>
-    public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasPreferredMetadataLanguage, IHasMetascore, IHasLookupInfo<TrailerInfo>
+    public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasProductionLocations, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasPreferredMetadataLanguage, IHasMetascore, IHasLookupInfo<TrailerInfo>
     {
         public List<Guid> SoundtrackIds { get; set; }
 
         public string PreferredMetadataLanguage { get; set; }
+        public List<string> ProductionLocations { get; set; }
 
         /// <summary>
         /// Gets or sets the preferred metadata country code.
@@ -31,6 +32,7 @@ namespace MediaBrowser.Controller.Entities
             SoundtrackIds = new List<Guid>();
             LocalTrailerIds = new List<Guid>();
             Keywords = new List<string>();
+            ProductionLocations = new List<string>();
         }
 
         public float? Metascore { get; set; }

+ 49 - 35
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -62,34 +62,6 @@ namespace MediaBrowser.Controller.Providers
                 ValidationType = ValidationType.None
             };
 
-            var hasTaglines = item as IHasTaglines;
-            if (hasTaglines != null)
-            {
-                hasTaglines.Taglines.Clear();
-            }
-
-            item.Studios.Clear();
-            item.Genres.Clear();
-            item.People.Clear();
-
-            var hasTags = item as IHasTags;
-            if (hasTags != null)
-            {
-                hasTags.Tags.Clear();
-            }
-
-            var hasKeywords = item as IHasKeywords;
-            if (hasKeywords != null)
-            {
-                hasKeywords.Keywords.Clear();
-            }
-
-            var hasTrailers = item as IHasTrailers;
-            if (hasTrailers != null)
-            {
-                hasTrailers.RemoteTrailers.Clear();
-            }
-
             //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
             Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
         }
@@ -373,6 +345,15 @@ namespace MediaBrowser.Controller.Providers
                         break;
                     }
 
+                case "Countries":
+                    {
+                        using (var subtree = reader.ReadSubtree())
+                        {
+                            FetchFromCountriesNode(subtree, item);
+                        }
+                        break;
+                    }
+
                 case "ContentRating":
                 case "MPAARating":
                     {
@@ -857,6 +838,42 @@ namespace MediaBrowser.Controller.Providers
             }
         }
 
+        private void FetchFromCountriesNode(XmlReader reader, T item)
+        {
+            reader.MoveToContent();
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Country":
+                            {
+                                var val = reader.ReadElementContentAsString();
+
+                                if (!string.IsNullOrWhiteSpace(val))
+                                {
+                                    var hasProductionLocations = item as IHasProductionLocations;
+                                    if (hasProductionLocations != null)
+                                    {
+                                        if (!string.IsNullOrWhiteSpace(val))
+                                        {
+                                            hasProductionLocations.AddProductionLocation(val);
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// Fetches from taglines node.
         /// </summary>
@@ -1059,16 +1076,13 @@ namespace MediaBrowser.Controller.Providers
             }
         }
 
-        protected async Task FetchChaptersFromXmlNode(BaseItem item, XmlReader reader, IItemRepository repository, CancellationToken cancellationToken)
+        protected List<ChapterInfo> FetchChaptersFromXmlNode(BaseItem item, XmlReader reader)
         {
-            var runtime = item.RunTimeTicks ?? 0;
-
             using (reader)
             {
-                var chapters = GetChaptersFromXmlNode(reader)
-                    .Where(i => i.StartPositionTicks >= 0 && i.StartPositionTicks < runtime);
-
-                await repository.SaveChapters(item.Id, chapters, cancellationToken).ConfigureAwait(false);
+                return GetChaptersFromXmlNode(reader)
+                    .Where(i => i.StartPositionTicks >= 0)
+                    .ToList();
             }
         }
 

+ 3 - 0
MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
@@ -35,10 +36,12 @@ namespace MediaBrowser.Controller.Providers
         public T Item { get; set; }
         
         public List<LocalImageInfo> Images { get; set; }
+        public List<ChapterInfo> Chapters { get; set; }
 
         public LocalMetadataResult()
         {
             Images = new List<LocalImageInfo>();
+            Chapters = new List<ChapterInfo>();
         }
     }
 }

+ 11 - 6
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -34,17 +34,22 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// Providers will be executed based on default rules
         /// </summary>
-        EnsureMetadata,
+        EnsureMetadata = 0,
 
         /// <summary>
         /// No providers will be executed
         /// </summary>
-        None,
+        None = 1,
 
         /// <summary>
         /// All providers will be executed to search for new metadata
         /// </summary>
-        FullRefresh
+        FullRefresh = 2,
+
+        /// <summary>
+        /// The validation only
+        /// </summary>
+        ValidationOnly = 3
     }
 
     public enum ImageRefreshMode
@@ -52,16 +57,16 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// The default
         /// </summary>
-        Default,
+        Default = 0,
 
         /// <summary>
         /// Existing images will be validated
         /// </summary>
-        ValidationOnly,
+        ValidationOnly = 1,
 
         /// <summary>
         /// All providers will be executed to search for new metadata
         /// </summary>
-        FullRefresh
+        FullRefresh = 2
     }
 }

+ 9 - 1
MediaBrowser.Controller/Session/ISessionController.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Session;
+using MediaBrowser.Model.System;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -13,6 +14,12 @@ namespace MediaBrowser.Controller.Session
         /// <value><c>true</c> if this instance is session active; otherwise, <c>false</c>.</value>
         bool IsSessionActive { get; }
 
+        /// <summary>
+        /// Gets a value indicating whether [supports media remote control].
+        /// </summary>
+        /// <value><c>true</c> if [supports media remote control]; otherwise, <c>false</c>.</value>
+        bool SupportsMediaControl { get; }
+        
         /// <summary>
         /// Sends the play command.
         /// </summary>
@@ -48,9 +55,10 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Sends the restart required message.
         /// </summary>
+        /// <param name="info">The information.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SendRestartRequiredNotification(CancellationToken cancellationToken);
+        Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken);
 
         /// <summary>
         /// Sends the user data change info.

+ 13 - 0
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -139,6 +139,19 @@ namespace MediaBrowser.Controller.Session
             }
         }
 
+        public bool SupportsMediaControl
+        {
+            get
+            {
+                if (SessionController != null)
+                {
+                    return SessionController.SupportsMediaControl;
+                }
+
+                return false;
+            }
+        }
+
         public bool ContainsUser(Guid userId)
         {
             return (UserId ?? Guid.Empty) == UserId || AdditionalUsers.Any(i => userId == new Guid(i.UserId));

+ 23 - 2
MediaBrowser.Controller/Subtitles/ISubtitleManager.cs

@@ -39,12 +39,33 @@ namespace MediaBrowser.Controller.Subtitles
         /// </summary>
         /// <param name="video">The video.</param>
         /// <param name="subtitleId">The subtitle identifier.</param>
-        /// <param name="providerName">Name of the provider.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task DownloadSubtitles(Video video, 
             string subtitleId, 
-            string providerName, 
             CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the remote subtitles.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{SubtitleResponse}.</returns>
+        Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Deletes the subtitles.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <param name="index">The index.</param>
+        /// <returns>Task.</returns>
+        Task DeleteSubtitles(string itemId, int index);
+
+        /// <summary>
+        /// Gets the providers.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <returns>IEnumerable{SubtitleProviderInfo}.</returns>
+        IEnumerable<SubtitleProviderInfo> GetProviders(string itemId);
     }
 }

+ 1 - 0
MediaBrowser.Controller/Subtitles/SubtitleResponse.cs

@@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Subtitles
     {
         public string Language { get; set; }
         public string Format { get; set; }
+        public bool IsForced { get; set; }
         public Stream Stream { get; set; }
     }
 }

+ 3 - 0
MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs

@@ -21,8 +21,11 @@ namespace MediaBrowser.Controller.Subtitles
         public long? RuntimeTicks { get; set; }
         public Dictionary<string, string> ProviderIds { get; set; }
 
+        public bool SearchAllProviders { get; set; }
+
         public SubtitleSearchRequest()
         {
+            SearchAllProviders = true;
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
     }

+ 1 - 1
MediaBrowser.Dlna/DlnaManager.cs

@@ -497,7 +497,7 @@ namespace MediaBrowser.Dlna
             var profile = GetProfile(headers) ??
                           GetDefaultProfile();
 
-            return new DescriptionXmlBuilder(profile, serverUuId).GetXml();
+            return new DescriptionXmlBuilder(profile, serverUuId, "").GetXml();
         }
 
         public DlnaIconResponse GetIcon(string filename)

+ 7 - 1
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -19,6 +19,7 @@ using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.System;
 
 namespace MediaBrowser.Dlna.PlayTo
 {
@@ -46,6 +47,11 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
+        public bool SupportsMediaControl
+        {
+            get { return IsSessionActive; }
+        }
+
         private Timer _updateTimer;
 
         public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler, string serverAddress)
@@ -315,7 +321,7 @@ namespace MediaBrowser.Dlna.PlayTo
             return Task.FromResult(true);
         }
 
-        public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+        public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken)
         {
             return Task.FromResult(true);
         }

+ 3 - 1
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -306,7 +306,9 @@ namespace MediaBrowser.Dlna.PlayTo
                             GeneralCommandType.Unmute.ToString(),
                             GeneralCommandType.ToggleMute.ToString(),
                             GeneralCommandType.SetVolume.ToString()
-                        }
+                        },
+
+                        SupportsMediaControl = true
                     });
 
                     _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);

+ 22 - 22
MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs

@@ -99,6 +99,13 @@ namespace MediaBrowser.Dlna.Server
         {
             var list = new List<StateVariable>();
 
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_Filter",
+                DataType = "string",
+                SendsEvents = false
+            });
+
             list.Add(new StateVariable
             {
                 Name = "A_ARG_TYPE_SortCriteria",
@@ -108,64 +115,64 @@ namespace MediaBrowser.Dlna.Server
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_UpdateID",
+                Name = "A_ARG_TYPE_Index",
                 DataType = "ui4",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_SearchCriteria",
-                DataType = "string",
+                Name = "A_ARG_TYPE_Count",
+                DataType = "ui4",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_Filter",
-                DataType = "string",
+                Name = "A_ARG_TYPE_UpdateID",
+                DataType = "ui4",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_Result",
+                Name = "SearchCapabilities",
                 DataType = "string",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_Index",
-                DataType = "ui4",
+                Name = "SortCapabilities",
+                DataType = "string",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_ObjectID",
-                DataType = "string",
-                SendsEvents = false
+                Name = "SystemUpdateID",
+                DataType = "ui4",
+                SendsEvents = true
             });
 
             list.Add(new StateVariable
             {
-                Name = "SortCapabilities",
+                Name = "A_ARG_TYPE_SearchCriteria",
                 DataType = "string",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "SearchCapabilities",
+                Name = "A_ARG_TYPE_Result",
                 DataType = "string",
                 SendsEvents = false
             });
 
             list.Add(new StateVariable
             {
-                Name = "A_ARG_TYPE_Count",
-                DataType = "ui4",
+                Name = "A_ARG_TYPE_ObjectID",
+                DataType = "string",
                 SendsEvents = false
             });
 
@@ -182,13 +189,6 @@ namespace MediaBrowser.Dlna.Server
                 }
             });
 
-            list.Add(new StateVariable
-            {
-                Name = "SystemUpdateID",
-                DataType = "ui4",
-                SendsEvents = true
-            });
-
             list.Add(new StateVariable
             {
                 Name = "A_ARG_TYPE_BrowseLetter",

+ 6 - 1
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -14,8 +14,9 @@ namespace MediaBrowser.Dlna.Server
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly string _serverUdn;
+        private readonly string _serverAddress;
 
-        public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn)
+        public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn, string serverAddress)
         {
             if (string.IsNullOrWhiteSpace(serverUdn))
             {
@@ -24,6 +25,7 @@ namespace MediaBrowser.Dlna.Server
 
             _profile = profile;
             _serverUdn = serverUdn;
+            _serverAddress = serverAddress;
         }
 
         public string GetXml()
@@ -67,6 +69,7 @@ namespace MediaBrowser.Dlna.Server
             builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>");
 
             builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + SecurityElement.Escape(_profile.XDlnaDoc ?? string.Empty) + "</dlna:X_DLNADOC>");
+            builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">M-DMS-1.50</dlna:X_DLNADOC>");
 
             builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>");
             builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
@@ -78,6 +81,8 @@ namespace MediaBrowser.Dlna.Server
             builder.Append("<modelURL>" + SecurityElement.Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>");
             builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber ?? string.Empty) + "</serialNumber>");
 
+            //builder.Append("<URLBase>" + SecurityElement.Escape(_serverAddress) + "</URLBase>");
+            
             if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags))
             {
                 builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + SecurityElement.Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>");

+ 2 - 2
MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs

@@ -9,11 +9,11 @@ namespace MediaBrowser.Dlna.Server
         {
             var list = new List<ServiceAction>
             {
-                GetGetSystemUpdateIDAction(),
                 GetSearchCapabilitiesAction(),
                 GetSortCapabilitiesAction(),
-                GetSearchAction(),
+                GetGetSystemUpdateIDAction(),
                 GetBrowseAction(),
+                GetSearchAction(),
                 GetX_GetFeatureListAction(),
                 GetXSetBookmarkAction(),
                 GetBrowseByLetterAction()

+ 0 - 91
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs

@@ -1,91 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
-    public class AudioEncoder
-    {
-        private readonly string _ffmpegPath;
-        private readonly ILogger _logger;
-        private readonly IFileSystem _fileSystem;
-        private readonly IApplicationPaths _appPaths;
-        private readonly IIsoManager _isoManager;
-        private readonly ILiveTvManager _liveTvManager;
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        public AudioEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
-        {
-            _ffmpegPath = ffmpegPath;
-            _logger = logger;
-            _fileSystem = fileSystem;
-            _appPaths = appPaths;
-            _isoManager = isoManager;
-            _liveTvManager = liveTvManager;
-        }
-
-        public Task BeginEncoding(InternalEncodingTask task)
-        {
-            return new FFMpegProcess(_ffmpegPath, _logger, _fileSystem, _appPaths, _isoManager, _liveTvManager).Start(task, GetArguments);
-        }
-
-        private string GetArguments(InternalEncodingTask task, string mountedPath)
-        {
-            var options = task.Request;
-
-            return string.Format("{0} -i {1} {2} -id3v2_version 3 -write_id3v1 1 \"{3}\"",
-                GetInputModifier(task),
-                GetInputArgument(task),
-                GetOutputModifier(task),
-                options.OutputPath).Trim();
-        }
-
-        private string GetInputModifier(InternalEncodingTask task)
-        {
-            return EncodingUtils.GetInputModifier(task);
-        }
-
-        private string GetInputArgument(InternalEncodingTask task)
-        {
-            return EncodingUtils.GetInputArgument(new List<string> { task.MediaPath }, task.IsInputRemote);
-        }
-
-        private string GetOutputModifier(InternalEncodingTask task)
-        {
-            var options = task.Request;
-
-            var audioTranscodeParams = new List<string>
-            {
-                "-threads " + EncodingUtils.GetNumberOfThreads(task, false).ToString(_usCulture),
-                "-vn"
-            };
-
-            var bitrate = EncodingUtils.GetAudioBitrateParam(task);
-
-            if (bitrate.HasValue)
-            {
-                audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
-            }
-
-            var channels = EncodingUtils.GetNumAudioChannelsParam(options, task.AudioStream);
-
-            if (channels.HasValue)
-            {
-                audioTranscodeParams.Add("-ac " + channels.Value);
-            }
-
-            if (options.AudioSampleRate.HasValue)
-            {
-                audioTranscodeParams.Add("-ar " + options.AudioSampleRate.Value);
-            }
-
-            return string.Join(" ", audioTranscodeParams.ToArray());
-        }
-    }
-}

+ 0 - 114
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs

@@ -64,77 +64,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return string.Format("\"{0}\"", url);
         }
 
-        public static string GetAudioInputModifier(InternalEncodingTask options)
-        {
-            return GetCommonInputModifier(options);
-        }
-
-        public static string GetInputModifier(InternalEncodingTask options)
-        {
-            var inputModifier = GetCommonInputModifier(options);
-
-            //if (state.VideoRequest != null)
-            //{
-            //    inputModifier += " -fflags genpts";
-            //}
-
-            //if (!string.IsNullOrEmpty(state.InputVideoCodec))
-            //{
-            //    inputModifier += " -vcodec " + state.InputVideoCodec;
-            //}
-
-            //if (!string.IsNullOrEmpty(state.InputVideoSync))
-            //{
-            //    inputModifier += " -vsync " + state.InputVideoSync;
-            //}
-
-            return inputModifier;
-        }
-
-        private static string GetCommonInputModifier(InternalEncodingTask options)
-        {
-            var inputModifier = string.Empty;
-
-            if (options.EnableDebugLogging)
-            {
-                inputModifier += "-loglevel debug";
-            }
-
-            var probeSize = GetProbeSizeArgument(options.InputVideoType.HasValue && options.InputVideoType.Value == VideoType.Dvd);
-            inputModifier += " " + probeSize;
-            inputModifier = inputModifier.Trim();
-
-            if (!string.IsNullOrWhiteSpace(options.UserAgent))
-            {
-                inputModifier += " -user-agent \"" + options.UserAgent + "\"";
-            }
-
-            inputModifier += " " + GetFastSeekValue(options.Request);
-            inputModifier = inputModifier.Trim();
-
-            if (!string.IsNullOrEmpty(options.InputFormat))
-            {
-                inputModifier += " -f " + options.InputFormat;
-            }
-
-            if (!string.IsNullOrEmpty(options.InputAudioCodec))
-            {
-                inputModifier += " -acodec " + options.InputAudioCodec;
-            }
-
-            if (!string.IsNullOrEmpty(options.InputAudioSync))
-            {
-                inputModifier += " -async " + options.InputAudioSync;
-            }
-
-            if (options.ReadInputAtNativeFramerate)
-            {
-                inputModifier += " -re";
-            }
-
-            return inputModifier;
-        }
-
         private static string GetFastSeekValue(EncodingOptions options)
         {
             var time = options.StartTimeTicks;
@@ -157,19 +86,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
         }
 
-        public static int? GetAudioBitrateParam(InternalEncodingTask task)
-        {
-            if (task.Request.AudioBitRate.HasValue)
-            {
-                // Make sure we don't request a bitrate higher than the source
-                var currentBitrate = task.AudioStream == null ? task.Request.AudioBitRate.Value : task.AudioStream.BitRate ?? task.Request.AudioBitRate.Value;
-
-                return Math.Min(currentBitrate, task.Request.AudioBitRate.Value);
-            }
-
-            return null;
-        }
-
         /// <summary>
         /// Gets the number of audio channels to specify on the command line
         /// </summary>
@@ -201,35 +117,5 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             return request.AudioChannels;
         }
-
-        public static int GetNumberOfThreads(InternalEncodingTask state, bool isWebm)
-        {
-            // Use more when this is true. -re will keep cpu usage under control
-            if (state.ReadInputAtNativeFramerate)
-            {
-                if (isWebm)
-                {
-                    return Math.Max(Environment.ProcessorCount - 1, 2);
-                }
-
-                return 0;
-            }
-
-            // Webm: http://www.webmproject.org/docs/encoder-parameters/
-            // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads 
-            // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
-
-            switch (state.QualitySetting)
-            {
-                case EncodingQuality.HighSpeed:
-                    return 2;
-                case EncodingQuality.HighQuality:
-                    return 2;
-                case EncodingQuality.MaxQuality:
-                    return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
-                default:
-                    throw new Exception("Unrecognized MediaEncodingQuality value.");
-            }
-        }
     }
 }

+ 0 - 168
MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs

@@ -1,168 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
-    public class FFMpegProcess : IDisposable
-    {
-        private readonly string _ffmpegPath;
-        private readonly ILogger _logger;
-        private readonly IFileSystem _fileSystem;
-        private readonly IApplicationPaths _appPaths;
-        private readonly IIsoManager _isoManager;
-        private readonly ILiveTvManager _liveTvManager;
-
-        private Stream _logFileStream;
-        private InternalEncodingTask _task;
-        private IIsoMount _isoMount;
-
-        public FFMpegProcess(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
-        {
-            _ffmpegPath = ffmpegPath;
-            _logger = logger;
-            _fileSystem = fileSystem;
-            _appPaths = appPaths;
-            _isoManager = isoManager;
-            _liveTvManager = liveTvManager;
-        }
-
-        public async Task Start(InternalEncodingTask task, Func<InternalEncodingTask,string,string> argumentsFactory)
-        {
-            _task = task;
-            if (!File.Exists(_ffmpegPath))
-            {
-                throw new InvalidOperationException("ffmpeg was not found at " + _ffmpegPath);
-            }
-
-            Directory.CreateDirectory(Path.GetDirectoryName(task.Request.OutputPath));
-
-            string mountedPath = null;
-            if (task.InputVideoType.HasValue && task.InputVideoType == VideoType.Iso && task.IsoType.HasValue)
-            {
-                if (_isoManager.CanMount(task.MediaPath))
-                {
-                    _isoMount = await _isoManager.Mount(task.MediaPath, CancellationToken.None).ConfigureAwait(false);
-                    mountedPath = _isoMount.MountedPath;
-                }
-            }
-            
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-
-                    // Must consume both stdout and stderr or deadlocks may occur
-                    RedirectStandardOutput = true,
-                    RedirectStandardError = true,
-
-                    FileName = _ffmpegPath,
-                    WorkingDirectory = Path.GetDirectoryName(_ffmpegPath),
-                    Arguments = argumentsFactory(task, mountedPath),
-
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                },
-
-                EnableRaisingEvents = true
-            };
-
-            _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
-
-            var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-" + task.Id + ".txt");
-            Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
-
-            // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
-            _logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
-
-            process.Exited += process_Exited;
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error starting ffmpeg", ex);
-
-                task.OnError();
-
-                DisposeLogFileStream();
-
-                process.Dispose();
-
-                throw;
-            }
-
-            task.OnBegin();
-
-            // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-            process.BeginOutputReadLine();
-
-#pragma warning disable 4014
-            // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            process.StandardError.BaseStream.CopyToAsync(_logFileStream);
-#pragma warning restore 4014
-        }
-
-        async void process_Exited(object sender, EventArgs e)
-        {
-            var process = (Process)sender;
-
-            if (_isoMount != null)
-            {
-                _isoMount.Dispose();
-                _isoMount = null;
-            }
-
-            DisposeLogFileStream();
-
-            try
-            {
-                _logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, _task.Request.OutputPath);
-            }
-            catch
-            {
-                _logger.Info("FFMpeg exited with an error for {0}", _task.Request.OutputPath);
-            }
-
-            _task.OnCompleted();
-            
-            if (!string.IsNullOrEmpty(_task.LiveTvStreamId))
-            {
-                try
-                {
-                    await _liveTvManager.CloseLiveStream(_task.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error closing live tv stream", ex);
-                }
-            }
-        }
-
-        public void Dispose()
-        {
-            DisposeLogFileStream();
-        }
-
-        private void DisposeLogFileStream()
-        {
-            if (_logFileStream != null)
-            {
-                _logFileStream.Dispose();
-                _logFileStream = null;
-            }
-        }
-    }
-}

+ 0 - 235
MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs

@@ -1,235 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
-    public class ImageEncoder
-    {
-        private readonly string _ffmpegPath;
-        private readonly ILogger _logger;
-        private readonly IFileSystem _fileSystem;
-        private readonly IApplicationPaths _appPaths;
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(10, 10);
-
-        public ImageEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
-        {
-            _ffmpegPath = ffmpegPath;
-            _logger = logger;
-            _fileSystem = fileSystem;
-            _appPaths = appPaths;
-        }
-
-        public async Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
-        {
-            ValidateInput(options);
-
-            await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                return await EncodeImageInternal(options, cancellationToken).ConfigureAwait(false);
-            }
-            finally
-            {
-                ResourcePool.Release();
-            }
-        }
-
-        private async Task<Stream> EncodeImageInternal(ImageEncodingOptions options, CancellationToken cancellationToken)
-        {
-            ValidateInput(options);
-
-            var inputPath = options.InputPath;
-            var filename = Path.GetFileName(inputPath);
-
-            if (HasDiacritics(filename))
-            {
-                inputPath = GetTempFile(inputPath);
-                filename = Path.GetFileName(inputPath);
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-                    FileName = _ffmpegPath,
-                    Arguments = GetArguments(options, filename),
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false,
-                    RedirectStandardOutput = true,
-                    RedirectStandardError = true,
-                    WorkingDirectory = Path.GetDirectoryName(inputPath)
-                }
-            };
-
-            _logger.Debug("ffmpeg " + process.StartInfo.Arguments);
-
-            process.Start();
-
-            var memoryStream = new MemoryStream();
-
-#pragma warning disable 4014
-            // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
-#pragma warning restore 4014
-
-            // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-            process.BeginErrorReadLine();
-
-            var ranToCompletion = process.WaitForExit(5000);
-
-            if (!ranToCompletion)
-            {
-                try
-                {
-                    _logger.Info("Killing ffmpeg process");
-
-                    process.Kill();
-
-                    process.WaitForExit(1000);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error killing process", ex);
-                }
-            }
-
-            var exitCode = ranToCompletion ? process.ExitCode : -1;
-
-            process.Dispose();
-
-            if (exitCode == -1 || memoryStream.Length == 0)
-            {
-                memoryStream.Dispose();
-
-                var msg = string.Format("ffmpeg image encoding failed for {0}", options.InputPath);
-
-                _logger.Error(msg);
-
-                throw new ApplicationException(msg);
-            }
-
-            memoryStream.Position = 0;
-            return memoryStream;
-        }
-
-        private string GetTempFile(string path)
-        {
-            var extension = Path.GetExtension(path) ?? string.Empty;
-
-            var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N") + extension);
-
-            File.Copy(path, tempPath);
-
-            return tempPath;
-        }
-        
-        private string GetArguments(ImageEncodingOptions options, string inputFilename)
-        {
-            var vfScale = GetFilterGraph(options);
-            var outputFormat = GetOutputFormat(options.Format);
-
-            var quality = (options.Quality ?? 100) * .3;
-            quality = 31 - quality;
-            var qualityValue = Convert.ToInt32(Math.Max(quality, 1));
-
-            return string.Format("-f image2 -i file:\"{3}\" -q:v {0} {1} -f image2pipe -vcodec {2} -",
-                qualityValue.ToString(_usCulture),
-                vfScale,
-                outputFormat,
-                inputFilename);
-        }
-
-        private string GetFilterGraph(ImageEncodingOptions options)
-        {
-            if (!options.Width.HasValue &&
-                !options.Height.HasValue &&
-                !options.MaxHeight.HasValue &&
-                !options.MaxWidth.HasValue)
-            {
-                return string.Empty;
-            }
-
-            var widthScale = "-1";
-            var heightScale = "-1";
-
-            if (options.MaxWidth.HasValue)
-            {
-                widthScale = "min(iw\\," + options.MaxWidth.Value.ToString(_usCulture) + ")";
-            }
-            else if (options.Width.HasValue)
-            {
-                widthScale = options.Width.Value.ToString(_usCulture);
-            }
-
-            if (options.MaxHeight.HasValue)
-            {
-                heightScale = "min(ih\\," + options.MaxHeight.Value.ToString(_usCulture) + ")";
-            }
-            else if (options.Height.HasValue)
-            {
-                heightScale = options.Height.Value.ToString(_usCulture);
-            }
-
-            var scaleMethod = "lanczos";
-
-            return string.Format("-vf scale=\"{0}:{1}\"",
-                widthScale,
-                heightScale);
-        }
-
-        private string GetOutputFormat(string format)
-        {
-            if (string.Equals(format, "jpeg", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(format, "jpg", StringComparison.OrdinalIgnoreCase))
-            {
-                return "mjpeg";
-            }
-            return format;
-        }
-
-        private void ValidateInput(ImageEncodingOptions options)
-        {
-
-        }
-
-        /// <summary>
-        /// Determines whether the specified text has diacritics.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
-        private bool HasDiacritics(string text)
-        {
-            return !String.Equals(text, RemoveDiacritics(text), StringComparison.Ordinal);
-        }
-
-        /// <summary>
-        /// Removes the diacritics.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <returns>System.String.</returns>
-        private string RemoveDiacritics(string text)
-        {
-            return String.Concat(
-                text.Normalize(NormalizationForm.FormD)
-                .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
-                                              UnicodeCategory.NonSpacingMark)
-              ).Normalize(NormalizationForm.FormC);
-        }
-    }
-}

+ 0 - 95
MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs

@@ -1,95 +0,0 @@
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
-    public class InternalEncodingTask
-    {
-        public string Id { get; set; }
-
-        public CancellationTokenSource CancellationTokenSource { get; set; }
-
-        public double ProgressPercentage { get; set; }
-
-        public EncodingOptions Request { get; set; }
-
-        public VideoEncodingOptions VideoRequest
-        {
-            get { return Request as VideoEncodingOptions; }
-        }
-
-        public string MediaPath { get; set; }
-        public List<string> StreamFileNames { get; set; }
-        public bool IsInputRemote { get; set; }
-
-        public VideoType? InputVideoType { get; set; }
-        public IsoType? IsoType { get; set; }
-        public long? InputRunTimeTicks;
-
-        public string AudioSync = "1";
-        public string VideoSync = "vfr";
-
-        public string InputAudioSync { get; set; }
-        public string InputVideoSync { get; set; }
-
-        public bool DeInterlace { get; set; }
-
-        public bool ReadInputAtNativeFramerate { get; set; }
-
-        public string InputFormat { get; set; }
-
-        public string InputVideoCodec { get; set; }
-
-        public string InputAudioCodec { get; set; }
-
-        public string LiveTvStreamId { get; set; }
-
-        public MediaStream AudioStream { get; set; }
-        public MediaStream VideoStream { get; set; }
-        public MediaStream SubtitleStream { get; set; }
-        public bool HasMediaStreams { get; set; }
-
-        public int SegmentLength = 10;
-        public int HlsListSize;
-
-        public string MimeType { get; set; }
-        public string OrgPn { get; set; }
-        public bool EnableMpegtsM2TsMode { get; set; }
-
-        /// <summary>
-        /// Gets or sets the user agent.
-        /// </summary>
-        /// <value>The user agent.</value>
-        public string UserAgent { get; set; }
-
-        public EncodingQuality QualitySetting { get; set; }
-
-        public InternalEncodingTask()
-        {
-            Id = Guid.NewGuid().ToString("N");
-            CancellationTokenSource = new CancellationTokenSource();
-            StreamFileNames = new List<string>();
-        }
-
-        public bool EnableDebugLogging { get; set; }
-
-        internal void OnBegin()
-        {
-            
-        }
-
-        internal void OnCompleted()
-        {
-            
-        }
-
-        internal void OnError()
-        {
-            
-        }
-    }
-}

+ 0 - 311
MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs

@@ -1,311 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
-    public class InternalEncodingTaskFactory
-    {
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILiveTvManager _liveTvManager;
-        private readonly IItemRepository _itemRepo;
-        private readonly IServerConfigurationManager _config;
-
-        public InternalEncodingTaskFactory(ILibraryManager libraryManager, ILiveTvManager liveTvManager, IItemRepository itemRepo, IServerConfigurationManager config)
-        {
-            _libraryManager = libraryManager;
-            _liveTvManager = liveTvManager;
-            _itemRepo = itemRepo;
-            _config = config;
-        }
-
-        public async Task<InternalEncodingTask> Create(EncodingOptions request, CancellationToken cancellationToken)
-        {
-            ValidateInput(request);
-
-            var state = new InternalEncodingTask
-            {
-                Request = request
-            };
-
-            var item = string.IsNullOrEmpty(request.MediaSourceId) ?
-                _libraryManager.GetItemById(new Guid(request.ItemId)) :
-                _libraryManager.GetItemById(new Guid(request.MediaSourceId));
-
-            if (item is ILiveTvRecording)
-            {
-                var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
-
-                if (string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
-                {
-                    state.InputVideoType = VideoType.VideoFile;
-                }
-
-                var path = recording.RecordingInfo.Path;
-                var mediaUrl = recording.RecordingInfo.Url;
-
-                if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
-                {
-                    var streamInfo = await _liveTvManager.GetRecordingStream(request.ItemId, cancellationToken).ConfigureAwait(false);
-
-                    state.LiveTvStreamId = streamInfo.Id;
-
-                    path = streamInfo.Path;
-                    mediaUrl = streamInfo.Url;
-                }
-
-                if (!string.IsNullOrEmpty(path) && File.Exists(path))
-                {
-                    state.MediaPath = path;
-                    state.IsInputRemote = false;
-                }
-                else if (!string.IsNullOrEmpty(mediaUrl))
-                {
-                    state.MediaPath = mediaUrl;
-                    state.IsInputRemote = true;
-                }
-
-                state.InputRunTimeTicks = recording.RunTimeTicks;
-                if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsInputRemote)
-                {
-                    await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
-                }
-
-                state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
-                state.AudioSync = "1000";
-                state.DeInterlace = true;
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
-            }
-            else if (item is LiveTvChannel)
-            {
-                var channel = _liveTvManager.GetInternalChannel(request.ItemId);
-
-                if (string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
-                {
-                    state.InputVideoType = VideoType.VideoFile;
-                }
-
-                var streamInfo = await _liveTvManager.GetChannelStream(request.ItemId, cancellationToken).ConfigureAwait(false);
-
-                state.LiveTvStreamId = streamInfo.Id;
-
-                if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path))
-                {
-                    state.MediaPath = streamInfo.Path;
-                    state.IsInputRemote = false;
-
-                    await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
-                }
-                else if (!string.IsNullOrEmpty(streamInfo.Url))
-                {
-                    state.MediaPath = streamInfo.Url;
-                    state.IsInputRemote = true;
-                }
-
-                state.ReadInputAtNativeFramerate = true;
-                state.AudioSync = "1000";
-                state.DeInterlace = true;
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
-            }
-            else
-            {
-                state.MediaPath = item.Path;
-                state.IsInputRemote = item.LocationType == LocationType.Remote;
-
-                var video = item as Video;
-
-                if (video != null)
-                {
-                    state.InputVideoType = video.VideoType;
-                    state.IsoType = video.IsoType;
-
-                    state.StreamFileNames = video.PlayableStreamFileNames.ToList();
-                }
-
-                state.InputRunTimeTicks = item.RunTimeTicks;
-            }
-
-            var videoRequest = request as VideoEncodingOptions;
-
-            var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
-            {
-                ItemId = item.Id
-
-            }).ToList();
-
-            if (videoRequest != null)
-            {
-                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
-                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
-                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
-
-                if (state.VideoStream != null && state.VideoStream.IsInterlaced)
-                {
-                    state.DeInterlace = true;
-                }
-            }
-            else
-            {
-                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
-            }
-
-            state.HasMediaStreams = mediaStreams.Count > 0;
-
-            state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
-            state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
-
-            state.QualitySetting = GetQualitySetting();
-
-            ApplyDeviceProfileSettings(state);
-
-            return state;
-        }
-
-        private void ValidateInput(EncodingOptions request)
-        {
-            if (string.IsNullOrWhiteSpace(request.ItemId))
-            {
-                throw new ArgumentException("ItemId is required.");
-            }
-            if (string.IsNullOrWhiteSpace(request.OutputPath))
-            {
-                throw new ArgumentException("OutputPath is required.");
-            }
-            if (string.IsNullOrWhiteSpace(request.Container))
-            {
-                throw new ArgumentException("Container is required.");
-            }
-            if (string.IsNullOrWhiteSpace(request.AudioCodec))
-            {
-                throw new ArgumentException("AudioCodec is required.");
-            }
-
-            var videoRequest = request as VideoEncodingOptions;
-
-            if (videoRequest == null)
-            {
-                return;
-            }
-        }
-
-        /// <summary>
-        /// Determines which stream will be used for playback
-        /// </summary>
-        /// <param name="allStream">All stream.</param>
-        /// <param name="desiredIndex">Index of the desired.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
-        /// <returns>MediaStream.</returns>
-        private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
-        {
-            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
-
-            if (desiredIndex.HasValue)
-            {
-                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
-
-                if (stream != null)
-                {
-                    return stream;
-                }
-            }
-
-            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
-            {
-                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
-                       streams.FirstOrDefault();
-            }
-
-            // Just return the first one
-            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
-        }
-
-        private void ApplyDeviceProfileSettings(InternalEncodingTask state)
-        {
-            var profile = state.Request.DeviceProfile;
-
-            if (profile == null)
-            {
-                // Don't use settings from the default profile. 
-                // Only use a specific profile if it was requested.
-                return;
-            }
-
-            var container = state.Request.Container;
-
-            var audioCodec = state.Request.AudioCodec;
-
-            if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
-            {
-                audioCodec = state.AudioStream.Codec;
-            }
-
-            var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
-
-            if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
-            {
-                videoCodec = state.VideoStream.Codec;
-            }
-
-            //var mediaProfile = state.VideoRequest == null ?
-            //    profile.GetAudioMediaProfile(container, audioCodec) :
-            //    profile.GetVideoMediaProfile(container, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
-
-            //if (mediaProfile != null)
-            //{
-            //    state.MimeType = mediaProfile.MimeType;
-            //    state.OrgPn = mediaProfile.OrgPn;
-            //}
-
-            //var transcodingProfile = state.VideoRequest == null ?
-            //    profile.GetAudioTranscodingProfile(container, audioCodec) :
-            //    profile.GetVideoTranscodingProfile(container, audioCodec, videoCodec);
-
-            //if (transcodingProfile != null)
-            //{
-            //    //state.EstimateContentLength = transcodingProfile.EstimateContentLength;
-            //    state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
-            //    //state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
-
-            //    if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.VideoProfile))
-            //    {
-            //        state.VideoRequest.VideoProfile = transcodingProfile.VideoProfile;
-            //    }
-            //}
-        }
-
-        private EncodingQuality GetQualitySetting()
-        {
-            var quality = _config.Configuration.MediaEncodingQuality;
-
-            if (quality == EncodingQuality.Auto)
-            {
-                var cpuCount = Environment.ProcessorCount;
-
-                if (cpuCount >= 4)
-                {
-                    //return EncodingQuality.HighQuality;
-                }
-
-                return EncodingQuality.HighSpeed;
-            }
-
-            return quality;
-        }
-    }
-}

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -853,7 +853,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
         {
-            return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);
+            throw new NotImplementedException();
         }
 
         /// <summary>

+ 0 - 5
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -53,12 +53,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="BdInfo\BdInfoExaminer.cs" />
-    <Compile Include="Encoder\AudioEncoder.cs" />
     <Compile Include="Encoder\EncodingUtils.cs" />
-    <Compile Include="Encoder\FFMpegProcess.cs" />
-    <Compile Include="Encoder\ImageEncoder.cs" />
-    <Compile Include="Encoder\InternalEncodingTask.cs" />
-    <Compile Include="Encoder\InternalEncodingTaskFactory.cs" />
     <Compile Include="Encoder\MediaEncoder.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Subtitles\ISubtitleParser.cs" />

+ 1 - 0
MediaBrowser.Model/Configuration/LiveTvOptions.cs

@@ -3,5 +3,6 @@
     public class LiveTvOptions
     {
         public int? GuideDays { get; set; }
+        public string ActiveService { get; set; }
     }
 }

+ 5 - 2
MediaBrowser.Model/Configuration/NotificationType.cs

@@ -6,6 +6,10 @@ namespace MediaBrowser.Model.Configuration
         ApplicationUpdateInstalled,
         AudioPlayback,
         GamePlayback,
+        VideoPlayback,
+        AudioPlaybackStopped,
+        GamePlaybackStopped,
+        VideoPlaybackStopped,
         InstallationFailed,
         PluginError,
         PluginInstalled,
@@ -14,7 +18,6 @@ namespace MediaBrowser.Model.Configuration
         NewLibraryContent,
         NewLibraryContentMultiple,
         ServerRestartRequired,
-        TaskFailed,
-        VideoPlayback
+        TaskFailed
     }
 }

+ 0 - 39
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -318,44 +318,5 @@ namespace MediaBrowser.Model.Dlna
             }
             return null;
         }
-
-        public ResponseProfile GetPhotoMediaProfile(string container, int? width, int? height)
-        {
-            container = (container ?? string.Empty).TrimStart('.');
-
-            foreach (var i in ResponseProfiles)
-            {
-                if (i.Type != DlnaProfileType.Photo)
-                {
-                    continue;
-                }
-
-                List<string> containers = i.GetContainers().ToList();
-                if (containers.Count > 0 && !containers.Contains(container, StringComparer.OrdinalIgnoreCase))
-                {
-                    continue;
-                }
-
-                ConditionProcessor conditionProcessor = new ConditionProcessor();
-
-                var anyOff = false;
-                foreach (ProfileCondition c in i.Conditions)
-                {
-                    if (!conditionProcessor.IsImageConditionSatisfied(c, width, height))
-                    {
-                        anyOff = true;
-                        break;
-                    }
-                }
-
-                if (anyOff)
-                {
-                    continue;
-                }
-
-                return i;
-            }
-            return null;
-        }
     }
 }

+ 6 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -133,6 +133,12 @@ namespace MediaBrowser.Model.Dto
         /// <value>The custom rating.</value>
         public string CustomRating { get; set; }
 
+        /// <summary>
+        /// Gets or sets the channel identifier.
+        /// </summary>
+        /// <value>The channel identifier.</value>
+        public string ChannelId { get; set; }
+        
         /// <summary>
         /// Gets or sets the overview.
         /// </summary>

+ 1 - 2
MediaBrowser.Model/Entities/LibraryUpdateInfo.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Model.Entities
 {

+ 6 - 0
MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs

@@ -16,4 +16,10 @@ namespace MediaBrowser.Model.Providers
         public int? DownloadCount { get; set; }
         public bool? IsHashMatch { get; set; }
     }
+
+    public class SubtitleProviderInfo
+    {
+        public string Name { get; set; }
+        public string Id { get; set; }
+    }
 }

+ 1 - 0
MediaBrowser.Model/Querying/ItemSortBy.cs

@@ -82,5 +82,6 @@ namespace MediaBrowser.Model.Querying
         public const string Studio = "Studio";
         public const string Players = "Players";
         public const string GameSystem = "GameSystem";
+        public const string IsFavoriteOrLiked = "IsFavoriteOrLiked";
     }
 }

+ 4 - 0
MediaBrowser.Model/Session/SessionCapabilities.cs

@@ -8,6 +8,10 @@ namespace MediaBrowser.Model.Session
 
         public List<string> SupportedCommands { get; set; }
 
+        public bool SupportsMediaControl { get; set; }
+
+        public string MessageCallbackUrl { get; set; }
+
         public SessionCapabilities()
         {
             PlayableMediaTypes = new List<string>();

+ 7 - 1
MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs

@@ -1,8 +1,10 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Movies;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 
@@ -20,7 +22,11 @@ namespace MediaBrowser.Providers.AdultVideos
 
         protected override void Fetch(LocalMetadataResult<AdultVideo> result, string path, CancellationToken cancellationToken)
         {
-            new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            var chapters = new List<ChapterInfo>();
+
+            new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken);
+
+            result.Chapters = chapters;
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -109,6 +109,7 @@
     <Compile Include="MediaInfo\FFProbeProvider.cs" />
     <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
     <Compile Include="MediaInfo\SubtitleDownloader.cs" />
+    <Compile Include="MediaInfo\SubtitleResolver.cs" />
     <Compile Include="Movies\MovieDbTrailerProvider.cs" />
     <Compile Include="Movies\MovieExternalIds.cs" />
     <Compile Include="Movies\TrailerMetadataService.cs" />

+ 2 - 2
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
 
-            return prober.ProbeVideo(item, directoryService, cancellationToken);
+            return prober.ProbeVideo(item, directoryService, true, cancellationToken);
         }
 
         public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)
@@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 {
                     var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
 
-                    return !video.SubtitleFiles.SequenceEqual(prober.GetSubtitleFiles(video, directoryService, false).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
+                    return !video.SubtitleFiles.SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, false).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
                 }
             }
 

+ 17 - 112
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.MediaInfo
             _subtitleManager = subtitleManager;
         }
 
-        public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
+        public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
             where T : Video
         {
             var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
@@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 cancellationToken.ThrowIfCancellationRequested();
 
-                await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService).ConfigureAwait(false);
+                await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService, enableSubtitleDownloading).ConfigureAwait(false);
 
             }
             finally
@@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
             return result;
         }
 
-        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService)
+        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService, bool enableSubtitleDownloading)
         {
             var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
             var mediaStreams = mediaInfo.MediaStreams;
@@ -208,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
             }
 
-            await AddExternalSubtitles(video, mediaStreams, directoryService, cancellationToken).ConfigureAwait(false);
+            await AddExternalSubtitles(video, mediaStreams, directoryService, enableSubtitleDownloading, cancellationToken).ConfigureAwait(false);
 
             FetchWtvInfo(video, data);
 
@@ -255,7 +255,9 @@ namespace MediaBrowser.Providers.MediaInfo
                 }
             }
 
-            info.StartPositionTicks = chapter.start / 100;
+            // Limit accuracy to milliseconds to match xml saving
+            var ms = Math.Round(TimeSpan.FromTicks(chapter.start / 100).TotalMilliseconds);
+            info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
 
             return info;
         }
@@ -409,60 +411,22 @@ namespace MediaBrowser.Providers.MediaInfo
             }
         }
 
-        private IEnumerable<string> SubtitleExtensions
-        {
-            get
-            {
-                return new[] { ".srt", ".ssa", ".ass", ".sub" };
-            }
-        }
-
-        public IEnumerable<FileSystemInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService, bool clearCache)
-        {
-            var containingPath = video.ContainingFolderPath;
-
-            if (string.IsNullOrEmpty(containingPath))
-            {
-                throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id));
-            }
-
-            var files = directoryService.GetFiles(containingPath, clearCache);
-
-            var videoFileNameWithoutExtension = Path.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);
-
-                    if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return true;
-                    }
-                    if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return true;
-                    }
-                }
-
-                return false;
-            });
-        }
-
         /// <summary>
         /// Adds the external subtitles.
         /// </summary>
         /// <param name="video">The video.</param>
         /// <param name="currentStreams">The current streams.</param>
-        private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, CancellationToken cancellationToken)
+        /// <param name="directoryService">The directory service.</param>
+        /// <param name="enableSubtitleDownloading">if set to <c>true</c> [enable subtitle downloading].</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
         {
-            var externalSubtitleStreams = GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList();
+            var subtitleResolver = new SubtitleResolver(_localization);
+
+            var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList();
 
-            if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
+            if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
                 video is Episode) ||
                 (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles &&
                 video is Movie))
@@ -480,7 +444,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 // Rescan
                 if (downloadedLanguages.Count > 0)
                 {
-                    externalSubtitleStreams = GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, true).ToList();
+                    externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, true).ToList();
                 }
             }
 
@@ -489,65 +453,6 @@ namespace MediaBrowser.Providers.MediaInfo
             currentStreams.AddRange(externalSubtitleStreams);
         }
 
-        private IEnumerable<MediaStream> GetExternalSubtitleStreams(Video video, 
-            int startIndex, 
-            IDirectoryService directoryService,
-            bool clearCache)
-        {
-            var files = GetSubtitleFiles(video, directoryService, clearCache);
-
-            var streams = new List<MediaStream>();
-
-            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
-
-            foreach (var file in files)
-            {
-                var fullName = file.FullName;
-
-                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
-
-                // If the subtitle file matches the video file name
-                if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
-                {
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
-                    });
-                }
-                else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
-                {
-                    // Support xbmc naming conventions - 300.spanish.srt
-                    var language = fileNameWithoutExtension.Split('.').LastOrDefault();
-
-                    // Try to translate to three character code
-                    // Be flexible and check against both the full and three character versions
-                    var culture = _localization.GetCultures()
-                        .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
-
-                    if (culture != null)
-                    {
-                        language = culture.ThreeLetterISOLanguageName;
-                    }
-
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
-                        Language = language
-                    });
-                }
-            }
-
-            return streams;
-        }
-
         /// <summary>
         /// The dummy chapter duration
         /// </summary>

+ 5 - 2
MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs

@@ -124,7 +124,10 @@ namespace MediaBrowser.Providers.MediaInfo
                 Name = video.Name,
                 ParentIndexNumber = video.ParentIndexNumber,
                 ProductionYear = video.ProductionYear,
-                ProviderIds = video.ProviderIds
+                ProviderIds = video.ProviderIds,
+
+                // Stop as soon as we find something
+                SearchAllProviders = false
             };
 
             var episode = video as Episode;
@@ -143,7 +146,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 if (result != null)
                 {
-                    await _subtitleManager.DownloadSubtitles(video, result.Id, result.ProviderName, cancellationToken)
+                    await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken)
                             .ConfigureAwait(false);
 
                     return true;

+ 135 - 0
MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs

@@ -0,0 +1,135 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+    public class SubtitleResolver
+    {
+        private readonly ILocalizationManager _localization;
+
+        public SubtitleResolver(ILocalizationManager localization)
+        {
+            _localization = localization;
+        }
+
+        public IEnumerable<MediaStream> GetExternalSubtitleStreams(Video video,
+          int startIndex,
+          IDirectoryService directoryService,
+          bool clearCache)
+        {
+            var files = GetSubtitleFiles(video, directoryService, clearCache);
+
+            var streams = new List<MediaStream>();
+
+            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
+
+            foreach (var file in files)
+            {
+                var fullName = file.FullName;
+
+                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
+
+                var codec = Path.GetExtension(fullName).ToLower().TrimStart('.');
+
+                // If the subtitle file matches the video file name
+                if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+                {
+                    streams.Add(new MediaStream
+                    {
+                        Index = startIndex++,
+                        Type = MediaStreamType.Subtitle,
+                        IsExternal = true,
+                        Path = fullName,
+                        Codec = codec
+                    });
+                }
+                else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+                {
+                    var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 ||
+                        fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1;
+
+                    // Support xbmc naming conventions - 300.spanish.srt
+                    var language = fileNameWithoutExtension
+                        .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase)
+                        .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase)
+                        .Split('.')
+                        .LastOrDefault();
+
+                    // Try to translate to three character code
+                    // Be flexible and check against both the full and three character versions
+                    var culture = _localization.GetCultures()
+                        .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
+
+                    if (culture != null)
+                    {
+                        language = culture.ThreeLetterISOLanguageName;
+                    }
+
+                    streams.Add(new MediaStream
+                    {
+                        Index = startIndex++,
+                        Type = MediaStreamType.Subtitle,
+                        IsExternal = true,
+                        Path = fullName,
+                        Codec = codec,
+                        Language = language,
+                        IsForced = isForced
+                    });
+                }
+            }
+
+            return streams;
+        }
+
+        private static IEnumerable<string> SubtitleExtensions
+        {
+            get
+            {
+                return new[] { ".srt", ".ssa", ".ass", ".sub" };
+            }
+        }
+
+        public static IEnumerable<FileSystemInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService, bool clearCache)
+        {
+            var containingPath = video.ContainingFolderPath;
+
+            if (string.IsNullOrEmpty(containingPath))
+            {
+                throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id));
+            }
+
+            var files = directoryService.GetFiles(containingPath, clearCache);
+
+            var videoFileNameWithoutExtension = Path.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);
+
+                    if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
+                    if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            });
+        }
+    }
+}

+ 12 - 0
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs

@@ -133,6 +133,18 @@ namespace MediaBrowser.Providers.Movies
                 }
             }
 
+            if (movieData.production_countries != null)
+            {
+                var hasProductionLocations = movie as IHasProductionLocations;
+                if (hasProductionLocations != null)
+                {
+                    hasProductionLocations.ProductionLocations = movieData
+                        .production_countries
+                        .Select(i => i.name)
+                        .ToList();
+                }
+            }
+
             movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
             movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
 

+ 12 - 4
MediaBrowser.Providers/Movies/MovieXmlParser.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
 using System.Threading;
 using System.Xml;
 
@@ -12,13 +14,20 @@ namespace MediaBrowser.Providers.Movies
     /// </summary>
     public class MovieXmlParser : BaseItemXmlParser<Video>
     {
+        private List<ChapterInfo> _chaptersFound;
+
         public MovieXmlParser(ILogger logger)
             : base(logger)
         {
         }
 
-        public void FetchAsync(Video item, string metadataFile, CancellationToken cancellationToken)
+        public void Fetch(Video item, 
+            List<ChapterInfo> chapters, 
+            string metadataFile, 
+            CancellationToken cancellationToken)
         {
+            _chaptersFound = chapters;
+
             Fetch(item, metadataFile, cancellationToken);
         }
 
@@ -32,7 +41,6 @@ namespace MediaBrowser.Providers.Movies
             switch (reader.Name)
             {
                 case "TmdbCollectionName":
-
                     {
                         var val = reader.ReadElementContentAsString();
                         var movie = item as Movie;
@@ -41,13 +49,13 @@ namespace MediaBrowser.Providers.Movies
                         {
                             movie.TmdbCollectionName = val;
                         }
-                        
+
                         break;
                     }
 
                 case "Chapters":
 
-                    //_chaptersTask = FetchChaptersFromXmlNode(item, reader.ReadSubtree(), _itemRepo, CancellationToken.None);
+                    _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree()));
                     break;
 
                 default:

+ 7 - 1
MediaBrowser.Providers/Movies/MovieXmlProvider.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 
@@ -19,7 +21,11 @@ namespace MediaBrowser.Providers.Movies
 
         protected override void Fetch(LocalMetadataResult<Movie> result, string path, CancellationToken cancellationToken)
         {
-            new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            var chapters = new List<ChapterInfo>();
+
+            new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken);
+
+            result.Chapters = chapters;
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 7 - 1
MediaBrowser.Providers/Movies/TrailerXmlProvider.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 
@@ -19,7 +21,11 @@ namespace MediaBrowser.Providers.Movies
 
         protected override void Fetch(LocalMetadataResult<Trailer> result, string path, CancellationToken cancellationToken)
         {
-            new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            var chapters = new List<ChapterInfo>();
+
+            new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken);
+
+            result.Chapters = chapters;
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 36 - 14
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -34,6 +34,7 @@ namespace MediaBrowser.Providers.Savers
                     
                     "Chapters",
                     "ContentRating",
+                    "Countries",
                     "CustomRating",
                     "CriticRating",
                     "CriticRatingSummary",
@@ -318,6 +319,22 @@ namespace MediaBrowser.Providers.Savers
                 }
             }
 
+            var hasProductionLocations = item as IHasProductionLocations;
+            if (hasProductionLocations != null)
+            {
+                if (hasProductionLocations.ProductionLocations.Count > 0)
+                {
+                    builder.Append("<Countries>");
+
+                    foreach (var name in hasProductionLocations.ProductionLocations)
+                    {
+                        builder.Append("<Country>" + SecurityElement.Escape(name) + "</Country>");
+                    }
+
+                    builder.Append("</Countries>");
+                }
+            }
+
             var hasDisplayOrder = item as IHasDisplayOrder;
             if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder))
             {
@@ -636,22 +653,27 @@ namespace MediaBrowser.Providers.Savers
         {
             var video = item as Video;
 
-            if (video != null && video.Video3DFormat.HasValue)
+            if (video != null)
             {
-                switch (video.Video3DFormat.Value)
+                AddChapters(video, builder, itemRepository);
+
+                if (video.Video3DFormat.HasValue)
                 {
-                    case Video3DFormat.FullSideBySide:
-                        builder.Append("<Format3D>FSBS</Format3D>");
-                        break;
-                    case Video3DFormat.FullTopAndBottom:
-                        builder.Append("<Format3D>FTAB</Format3D>");
-                        break;
-                    case Video3DFormat.HalfSideBySide:
-                        builder.Append("<Format3D>HSBS</Format3D>");
-                        break;
-                    case Video3DFormat.HalfTopAndBottom:
-                        builder.Append("<Format3D>HTAB</Format3D>");
-                        break;
+                    switch (video.Video3DFormat.Value)
+                    {
+                        case Video3DFormat.FullSideBySide:
+                            builder.Append("<Format3D>FSBS</Format3D>");
+                            break;
+                        case Video3DFormat.FullTopAndBottom:
+                            builder.Append("<Format3D>FTAB</Format3D>");
+                            break;
+                        case Video3DFormat.HalfSideBySide:
+                            builder.Append("<Format3D>HSBS</Format3D>");
+                            break;
+                        case Video3DFormat.HalfTopAndBottom:
+                            builder.Append("<Format3D>HTAB</Format3D>");
+                            break;
+                    }
                 }
             }
         }

+ 138 - 10
MediaBrowser.Providers/Subtitles/SubtitleManager.cs

@@ -1,8 +1,10 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Model.Entities;
@@ -23,12 +25,16 @@ namespace MediaBrowser.Providers.Subtitles
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryMonitor _monitor;
+        private readonly ILibraryManager _libraryManager;
+        private readonly IItemRepository _itemRepo;
 
-        public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor)
+        public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IItemRepository itemRepo)
         {
             _logger = logger;
             _fileSystem = fileSystem;
             _monitor = monitor;
+            _libraryManager = libraryManager;
+            _itemRepo = itemRepo;
         }
 
         public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
@@ -38,15 +44,45 @@ namespace MediaBrowser.Providers.Subtitles
 
         public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
         {
+            var contentType = request.ContentType;
             var providers = _subtitleProviders
-                .Where(i => i.SupportedMediaTypes.Contains(request.ContentType))
+                .Where(i => i.SupportedMediaTypes.Contains(contentType))
                 .ToList();
 
+            // If not searching all, search one at a time until something is found
+            if (!request.SearchAllProviders)
+            {
+                foreach (var provider in providers)
+                {
+                    try
+                    {
+                        var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false);
+
+                        var list = searchResults.ToList();
+
+                        if (list.Count > 0)
+                        {
+                            Normalize(list);
+                            return list;
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name);
+                    }
+                }
+                return new List<RemoteSubtitleInfo>();
+            }
+
             var tasks = providers.Select(async i =>
             {
                 try
                 {
-                    return await i.Search(request, cancellationToken).ConfigureAwait(false);
+                    var searchResults = await i.Search(request, cancellationToken).ConfigureAwait(false);
+
+                    var list = searchResults.ToList();
+                    Normalize(list);
+                    return list;
                 }
                 catch (Exception ex)
                 {
@@ -62,17 +98,21 @@ namespace MediaBrowser.Providers.Subtitles
 
         public async Task DownloadSubtitles(Video video,
             string subtitleId,
-            string providerName,
             CancellationToken cancellationToken)
         {
-            var provider = _subtitleProviders.First(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
-
-            var response = await provider.GetSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
+            var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
 
             using (var stream = response.Stream)
             {
-                var savePath = Path.Combine(Path.GetDirectoryName(video.Path), 
-                    Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower() + "." + response.Format.ToLower());
+                var savePath = Path.Combine(Path.GetDirectoryName(video.Path),
+                    Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower());
+
+                if (response.IsForced)
+                {
+                    savePath += ".forced";
+                }
+
+                savePath += "." + response.Format.ToLower();
 
                 _logger.Info("Saving subtitles to {0}", savePath);
 
@@ -139,5 +179,93 @@ namespace MediaBrowser.Providers.Subtitles
 
             return SearchSubtitles(request, cancellationToken);
         }
+
+        private void Normalize(IEnumerable<RemoteSubtitleInfo> subtitles)
+        {
+            foreach (var sub in subtitles)
+            {
+                sub.Id = GetProviderId(sub.ProviderName) + "_" + sub.Id;
+            }
+        }
+
+        private string GetProviderId(string name)
+        {
+            return name.ToLower().GetMD5().ToString("N");
+        }
+
+        private ISubtitleProvider GetProvider(string id)
+        {
+            return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
+        }
+
+        public Task DeleteSubtitles(string itemId, int index)
+        {
+            var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+            {
+                Index = index,
+                ItemId = new Guid(itemId),
+                Type = MediaStreamType.Subtitle
+
+            }).First();
+
+            var path = stream.Path;
+            _monitor.ReportFileSystemChangeBeginning(path);
+
+            try
+            {
+                File.Delete(path);
+            }
+            finally
+            {
+                _monitor.ReportFileSystemChangeComplete(path, false);
+            }
+
+            return _libraryManager.GetItemById(itemId).RefreshMetadata(new MetadataRefreshOptions
+            {
+                ImageRefreshMode = ImageRefreshMode.ValidationOnly,
+                MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
+
+            }, CancellationToken.None);
+        }
+
+        public Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken)
+        {
+            var parts = id.Split(new[] { '_' }, 2);
+
+            var provider = GetProvider(parts.First());
+            id = parts.Last();
+
+            return provider.GetSubtitles(id, cancellationToken);
+        }
+
+        public IEnumerable<SubtitleProviderInfo> GetProviders(string itemId)
+        {
+            var video = _libraryManager.GetItemById(itemId) as Video;
+            VideoContentType mediaType;
+
+            if (video is Episode)
+            {
+                mediaType = VideoContentType.Episode;
+            }
+            else if (video is Movie)
+            {
+                mediaType = VideoContentType.Movie;
+            }
+            else
+            {
+                // These are the only supported types
+                return new List<SubtitleProviderInfo>();
+            }
+
+            var providers = _subtitleProviders
+                .Where(i => i.SupportedMediaTypes.Contains(mediaType))
+                .ToList();
+
+            return providers.Select(i => new SubtitleProviderInfo
+            {
+                Name = i.Name,
+                Id = GetProviderId(i.Name)
+            });
+        }
     }
 }

+ 8 - 2
MediaBrowser.Providers/TV/EpisodeXmlParser.cs

@@ -17,6 +17,7 @@ namespace MediaBrowser.Providers.TV
     public class EpisodeXmlParser : BaseItemXmlParser<Episode>
     {
         private List<LocalImageInfo> _imagesFound;
+        private List<ChapterInfo> _chaptersFound;
 
         public EpisodeXmlParser(ILogger logger)
             : base(logger)
@@ -25,9 +26,14 @@ namespace MediaBrowser.Providers.TV
 
         private string _xmlPath;
 
-        public void Fetch(Episode item, List<LocalImageInfo> images, string metadataFile, CancellationToken cancellationToken)
+        public void Fetch(Episode item, 
+            List<LocalImageInfo> images,
+            List<ChapterInfo> chapters, 
+            string metadataFile, 
+            CancellationToken cancellationToken)
         {
             _imagesFound = images;
+            _chaptersFound = chapters;
             _xmlPath = metadataFile;
 
             Fetch(item, metadataFile, cancellationToken);
@@ -46,7 +52,7 @@ namespace MediaBrowser.Providers.TV
             {
                 case "Chapters":
 
-                    //_chaptersTask = FetchChaptersFromXmlNode(item, reader.ReadSubtree(), _itemRepo, CancellationToken.None);
+                    _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree()));
                     break;
 
                 case "Episode":

+ 4 - 1
MediaBrowser.Providers/TV/EpisodeXmlProvider.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System.Collections.Generic;
 using System.IO;
@@ -21,10 +22,12 @@ namespace MediaBrowser.Providers.TV
         protected override void Fetch(LocalMetadataResult<Episode> result, string path, CancellationToken cancellationToken)
         {
             var images = new List<LocalImageInfo>();
+            var chapters = new List<ChapterInfo>();
 
-            new EpisodeXmlParser(_logger).Fetch(result.Item, images, path, cancellationToken);
+            new EpisodeXmlParser(_logger).Fetch(result.Item, images, chapters, path, cancellationToken);
 
             result.Images = images;
+            result.Chapters = chapters;
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 57 - 15
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -98,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.Channels
             {
                 all = all.Take(query.Limit.Value).ToList();
             }
-            
+
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields))
                     .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
@@ -156,6 +156,24 @@ namespace MediaBrowser.Server.Implementations.Channels
             progress.Report(100);
         }
 
+        public Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken)
+        {
+            var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
+
+            var channelGuid = new Guid(item.ChannelId);
+            var channel = _channelEntities.First(i => i.Id == channelGuid);
+            var channelPlugin = GetChannelProvider(channel);
+
+            var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
+
+            if (requiresCallback != null)
+            {
+                return requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken);
+            }
+
+            return Task.FromResult<IEnumerable<ChannelMediaInfo>>(item.ChannelMediaSources);
+        }
+
         private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
         {
             var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.Name));
@@ -246,7 +264,9 @@ namespace MediaBrowser.Server.Implementations.Channels
 
                 var channelId = channel.Id.ToString("N");
 
-                var tasks = items.Select(i => GetChannelItemEntity(i, channelId, cancellationToken));
+                var channelPlugin = GetChannelProvider(channel);
+
+                var tasks = items.Select(i => GetChannelItemEntity(i, channelPlugin, channelId, cancellationToken));
 
                 return await Task.WhenAll(tasks).ConfigureAwait(false);
             });
@@ -303,10 +323,16 @@ namespace MediaBrowser.Server.Implementations.Channels
 
                 var query = new InternalChannelItemQuery
                 {
-                    User = user,
-                    CategoryId = categoryId
+                    User = user
                 };
 
+                if (!string.IsNullOrWhiteSpace(categoryId))
+                {
+                    var categoryItem = (IChannelItem)_libraryManager.GetItemById(new Guid(categoryId));
+
+                    query.CategoryId = categoryItem.ExternalId;
+                }
+
                 var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
 
                 CacheResponse(result, cachePath);
@@ -339,7 +365,9 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             var categoryKey = string.IsNullOrWhiteSpace(categoryId) ? "root" : categoryId.GetMD5().ToString("N");
 
-            return Path.Combine(_config.ApplicationPaths.CachePath, "channels", channelId, categoryKey, user.Id.ToString("N") + ".json");
+            var version = string.IsNullOrWhiteSpace(channel.DataVersion) ? "0" : channel.DataVersion;
+
+            return Path.Combine(_config.ApplicationPaths.CachePath, "channels", channelId, version, categoryKey, user.Id.ToString("N") + ".json");
         }
 
         private async Task<QueryResult<BaseItemDto>> GetReturnItems(IEnumerable<BaseItem> items, User user, ChannelItemQuery query, CancellationToken cancellationToken)
@@ -377,21 +405,29 @@ namespace MediaBrowser.Server.Implementations.Channels
             };
         }
 
-        private string GetIdToHash(string externalId)
+        private string GetIdToHash(string externalId, IChannel channelProvider)
         {
             // Increment this as needed to force new downloads
-            return externalId + "4";
+            // Incorporate Name because it's being used to convert channel entity to provider
+            return externalId + (channelProvider.DataVersion ?? string.Empty) + (channelProvider.Name ?? string.Empty) + "11";
         }
 
-        private async Task<BaseItem> GetChannelItemEntity(ChannelItemInfo info, string internalChannnelId, CancellationToken cancellationToken)
+        private async Task<BaseItem> GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, string internalChannnelId, CancellationToken cancellationToken)
         {
+            if (string.IsNullOrEmpty(internalChannnelId))
+            {
+                throw new ArgumentNullException("internalChannnelId");
+            }
+
             BaseItem item;
             Guid id;
             var isNew = false;
 
+            var idToHash = GetIdToHash(info.Id, channelProvider);
+
             if (info.Type == ChannelItemType.Category)
             {
-                id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
+                id = idToHash.GetMBId(typeof(ChannelCategoryItem));
 
                 item = _libraryManager.GetItemById(id) as ChannelCategoryItem;
 
@@ -403,7 +439,7 @@ namespace MediaBrowser.Server.Implementations.Channels
             }
             else if (info.MediaType == ChannelMediaType.Audio)
             {
-                id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
+                id = idToHash.GetMBId(typeof(ChannelCategoryItem));
 
                 item = _libraryManager.GetItemById(id) as ChannelAudioItem;
 
@@ -415,7 +451,7 @@ namespace MediaBrowser.Server.Implementations.Channels
             }
             else
             {
-                id = GetIdToHash(info.Id).GetMBId(typeof(ChannelVideoItem));
+                id = idToHash.GetMBId(typeof(ChannelVideoItem));
 
                 item = _libraryManager.GetItemById(id) as ChannelVideoItem;
 
@@ -429,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.Channels
             item.Id = id;
             item.RunTimeTicks = info.RunTimeTicks;
 
-            var mediaSource = info.MediaSources.FirstOrDefault();
-
-            item.Path = mediaSource == null ? null : mediaSource.Path;
-
             if (isNew)
             {
                 item.Name = info.Name;
@@ -459,12 +491,22 @@ namespace MediaBrowser.Server.Implementations.Channels
             channelItem.ChannelId = internalChannnelId;
             channelItem.ChannelItemType = info.Type;
 
+            if (isNew)
+            {
+                channelItem.Tags = info.Tags;
+            }
+
             var channelMediaItem = item as IChannelMediaItem;
 
             if (channelMediaItem != null)
             {
                 channelMediaItem.IsInfiniteStream = info.IsInfiniteStream;
                 channelMediaItem.ContentType = info.ContentType;
+                channelMediaItem.ChannelMediaSources = info.MediaSources;
+
+                var mediaSource = info.MediaSources.FirstOrDefault();
+
+                item.Path = mediaSource == null ? null : mediaSource.Path;
             }
 
             if (isNew)

+ 8 - 0
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
@@ -1140,6 +1141,13 @@ namespace MediaBrowser.Server.Implementations.Dto
             {
                 dto.MediaSources = GetMediaSources(tvChannel);
             }
+
+            var channelItem = item as IChannelItem;
+
+            if (channelItem != null)
+            {
+                dto.ChannelId = channelItem.ChannelId;
+            }
         }
 
         public List<MediaSourceInfo> GetMediaSources(BaseItem item)

+ 1 - 1
MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         /// <summary>
         /// The library update duration
         /// </summary>
-        private const int LibraryUpdateDuration = 20000;
+        private const int LibraryUpdateDuration = 5000;
 
         public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger)
         {

+ 50 - 8
MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs

@@ -1,6 +1,4 @@
-using System.Globalization;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller;
@@ -20,6 +18,7 @@ using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Updates;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -71,6 +70,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             _userManager.UserCreated += _userManager_UserCreated;
             _libraryManager.ItemAdded += _libraryManager_ItemAdded;
             _sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
+            _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
             _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
             _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
             _appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
@@ -164,18 +164,42 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             await SendNotification(notification).ConfigureAwait(false);
         }
 
-        async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
-        {
-            var user = e.Users.FirstOrDefault();
+         void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
+         {
+             var item = e.MediaInfo;
+
+             if (item == null)
+             {
+                 _logger.Warn("PlaybackStart reported with null media info.");
+                 return;
+             }
+
+             var type = GetPlaybackNotificationType(item.MediaType);
 
+             SendPlaybackNotification(type, e);
+         }
+
+        void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
+        {
             var item = e.MediaInfo;
 
             if (item == null)
             {
-                _logger.Warn("PlaybackStart reported with null media info.");
+                _logger.Warn("PlaybackStopped reported with null media info.");
                 return;
             }
 
+            var type = GetPlaybackStoppedNotificationType(item.MediaType);
+
+            SendPlaybackNotification(type, e);
+        }
+
+        private async void SendPlaybackNotification(string type, PlaybackProgressEventArgs e)
+        {
+            var user = e.Users.FirstOrDefault();
+
+            var item = e.MediaInfo;
+
             if (e.Item != null && e.Item.Parent == null)
             {
                 // Don't report theme song or local trailer playback
@@ -185,7 +209,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
 
             var notification = new NotificationRequest
             {
-                NotificationType = GetPlaybackNotificationType(item.MediaType),
+                NotificationType = type,
 
                 ExcludeUserIds = e.Users.Select(i => i.Id.ToString("N")).ToList()
             };
@@ -216,6 +240,24 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             return null;
         }
 
+        private string GetPlaybackStoppedNotificationType(string mediaType)
+        {
+            if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
+            {
+                return NotificationType.AudioPlaybackStopped.ToString();
+            }
+            if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
+            {
+                return NotificationType.GamePlaybackStopped.ToString();
+            }
+            if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+            {
+                return NotificationType.VideoPlaybackStopped.ToString();
+            }
+
+            return null;
+        }
+
         private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
         void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
         {

+ 5 - 1
MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Net.WebSockets;
@@ -19,6 +20,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// </summary>
         private readonly ILogger _logger;
 
+        public event EventHandler<EventArgs> Closed;
+
         /// <summary>
         /// Gets or sets the web socket.
         /// </summary>
@@ -97,6 +100,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 if (bytes == null)
                 {
                     // Connection closed
+                    EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
                     break;
                 }
 

+ 9 - 1
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -84,7 +84,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
             _services.AddRange(services);
 
-            SetActiveService(_services.FirstOrDefault());
+            SetActiveService(_config.Configuration.LiveTvOptions.ActiveService);
+        }
+
+        private void SetActiveService(string name)
+        {
+            var service = _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)) ??
+                _services.FirstOrDefault();
+
+            SetActiveService(service);
         }
 
         private void SetActiveService(ILiveTvService service)

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/ar.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u0645\u0641\u0636\u0644\u0629 \u0644\u0644\u0635\u0648\u062a:",
     "LabelSubtitleLanguagePreference": "\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u0645\u0641\u0636\u0644\u0629 \u0644\u0644\u062a\u0631\u062c\u0645\u0629:",
-    "LabelDisplayForcedSubtitlesOnly": "\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u062a\u0631\u062c\u0645\u0627\u062a \u0627\u0644\u0642\u0633\u0631\u064a\u0629",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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": "\u0633\u062c\u0644 (\u0646\u0628\u0630\u0629)",
     "TabSecurity": "\u062d\u0645\u0627\u064a\u0629",
     "ButtonAddUser": "\u0627\u0636\u0627\u0641\u0629 \u0645\u0633\u062a\u062e\u062f\u0645",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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",
@@ -270,8 +283,8 @@
     "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 - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customize options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/ca.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Audio language preference:",
     "LabelSubtitleLanguagePreference": "Subtitle language preference:",
-    "LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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",
@@ -270,8 +283,8 @@
     "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 - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customize options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 47 - 13
MediaBrowser.Server.Implementations/Localization/Server/cs.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Up\u0159ednost\u0148ovan\u00fd jazyk videa:",
     "LabelSubtitleLanguagePreference": "Up\u0159ednost\u0148ovan\u00fd jazyk titulk\u016f:",
-    "LabelDisplayForcedSubtitlesOnly": "Zobrazit pouze vynucen\u00e9 titulky",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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": "Profily",
     "TabSecurity": "Zabezpe\u010den\u00ed",
     "ButtonAddUser": "P\u0159idat u\u017eivatele",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Vlastnosti:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Titulky",
     "OptionHasTrailer": "Uk\u00e1zka\/trailer",
     "OptionHasThemeSong": "Tematick\u00e1 hudba",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Hled\u00e1n\u00ed",
     "ButtonGroupVersions": "Skupinov\u00e9 verze",
     "PismoMessage": "Vyu\u017e\u00edv\u00e1me spr\u00e1vce soubor\u016f \"Pismo\" skrze dotovanou licenci.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "Pros\u00edm podpo\u0159te dal\u0161\u00ed bezplatn\u00e9 produkty, kter\u00e9 vyu\u017e\u00edv\u00e1me:",
     "VersionNumber": "Verze {0}",
     "TabPaths": "Cesty",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "Heslo nen\u00ed nutn\u00e9, pokud se p\u0159ihla\u0161ujete z m\u00edstn\u00edho PC-",
     "TabGuide": "Pr\u016fvodce",
     "TabChannels": "Kan\u00e1ly",
+    "TabCollections": "Collections",
     "HeaderChannels": "Kan\u00e1ly",
     "TabRecordings": "Nahran\u00e9",
     "TabScheduled": "Napl\u00e1nov\u00e1no",
     "TabSeries": "S\u00e9rie",
+    "TabFavorites": "Favorites",
+    "TabMyLibrary": "My Library",
     "ButtonCancelRecording": "Zru\u0161it nahr\u00e1v\u00e1n\u00ed",
     "HeaderPrePostPadding": "P\u0159ed\/po nahr\u00e1v\u00e1n\u00ed",
     "LabelPrePaddingMinutes": "Minuty nahr\u00e1van\u00e9 p\u0159ed za\u010d\u00e1tkem nahr\u00e1v\u00e1n\u00ed",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "Auto",
     "LiveTvPluginRequired": "P\u0159ed pokra\u010dov\u00e1n\u00edm je vy\u017eadov\u00e1n plugin TV poskytovatele.",
     "LiveTvPluginRequiredHelp": "Pros\u00edm nainstalujte jeden z dostupn\u00fdch plugin\u016f, jako Next PVR nebo ServerWmc",
-    "HeaderCustomizeOptionsPerMediaType": "Upravit nastaven\u00ed podle typu m\u00e9dia",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Miniatura",
     "OptionDownloadMenuImage": "Nab\u00eddka",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Prim\u00e1rn\u00ed",
     "HeaderFetchImages": "Na\u010d\u00edst obr\u00e1zky:",
     "HeaderImageSettings": "Nastaven\u00ed obr\u00e1zk\u016f",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "Maxim\u00e1ln\u00ed po\u010det obr\u00e1zk\u016f na pozad\u00ed pro polo\u017eku:",
     "LabelMaxScreenshotsPerItem": "Maxim\u00e1ln\u00ed po\u010det screenshot\u016f:",
     "LabelMinBackdropDownloadWidth": "Maxim\u00e1ln\u00ed \u0161\u00ed\u0159ka pozad\u00ed:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/da.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Audio language preference:",
     "LabelSubtitleLanguagePreference": "Subtitle language preference:",
-    "LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "Ingen undertekster",
+    "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",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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",
@@ -270,8 +283,8 @@
     "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 - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customize options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "H\u00f8jre",
     "ButtonBack": "Tilbage",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Side op",
     "ButtonPageDown": "Side ned",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "Ingen undertekster",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "Mit bibliotek",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Undertekster",
     "LabelOpenSubtitlesUsername": "Open Subtitles brugernavn:",
     "LabelOpenSubtitlesPassword": "Open Subtitles kode:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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 dette for at sikre at alle videoer har undertekster, uanset hvilket sprog lydsporet anvender.",
     "HeaderSendMessage": "Send besked",
     "ButtonSend": "Send",
-    "LabelMessageText": "Tekst besked"
+    "LabelMessageText": "Tekst besked",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 75 - 41
MediaBrowser.Server.Implementations/Localization/Server/de.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Wiedergabe Einstellungen",
     "LabelAudioLanguagePreference": "Audiosprache Einstellungen:",
     "LabelSubtitleLanguagePreference": "Untertitelsprache Einstellungen:",
-    "LabelDisplayForcedSubtitlesOnly": "Zeige nur erzwungene Untertitel",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "Keine Untertitel",
+    "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": "Profile",
     "TabSecurity": "Sicherheit",
     "ButtonAddUser": "User hinzuf\u00fcgen",
@@ -76,7 +83,7 @@
     "LabelMaxParentalRating": "H\u00f6chste erlaubte elterlich Bewertung:",
     "MaxParentalRatingHelp": "Inhalt mit einer h\u00f6heren Bewertung wird dem User nicht angezeigt.",
     "LibraryAccessHelp": "W\u00e4hlen Sie die Medienordner die Sie mit diesem Benutzer teilen m\u00f6chten. Administratoren k\u00f6nnen den Metadata-Manager verwenden um alle Ordner zu bearbeiten.",
-    "ChannelAccessHelp": "Select the channels to share with this user. Administrators will be able to edit all channels using the metadata manager.",
+    "ChannelAccessHelp": "W\u00e4hlen Sie die Kan\u00e4le, die mit diesem Benutzer geteilt werden sollen. Administratoren sind in der Lage alle K\u00e4nale \u00fcber den Metadata Manager zu bearbeiten.",
     "ButtonDeleteImage": "L\u00f6sche Bild",
     "LabelSelectUsers": "W\u00e4hle Benutzer:",
     "ButtonUpload": "Hochladen",
@@ -161,6 +168,10 @@
     "OptionIso": "ISO",
     "Option3D": "3D",
     "LabelFeatures": "Merkmal:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Untertitel",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Titellied",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Suche",
     "ButtonGroupVersions": "Gruppiere Versionen",
     "PismoMessage": "Verwendet Pismo File Mount durch eine gespendete Lizenz.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "Bitte unterst\u00fctzen Sie andere freie Produkte die wir benutzen:",
     "VersionNumber": "Version {0}",
     "TabPaths": "Pfade",
@@ -270,8 +283,8 @@
     "ButtonAutoScroll": "Auto-scroll",
     "LabelImageSavingConvention": "Speicherconvention der Bilddatein:",
     "LabelImageSavingConventionHelp": "Media Browser erkennt Bilddateien von den meisten gro\u00dfen Medienanwendungen. Die Auswahl ihrer Downloadconvention ist n\u00fctzlich, wenn Sie auch andere Produkte benutzen.",
-    "OptionImageSavingCompatible": "Kompatibel - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Kompatibel - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Einloggen",
     "TitleSignIn": "Einloggen",
     "HeaderPleaseSignIn": "Bitte einloggen",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "Passw\u00f6rter werden nich gebraucht, wenn Sie sich vom Localhost aus einloggen.",
     "TabGuide": "Programm",
     "TabChannels": "Kan\u00e4le",
+    "TabCollections": "Collections",
     "HeaderChannels": "Kan\u00e4le",
     "TabRecordings": "Aufnahmen",
     "TabScheduled": "Geplant",
     "TabSeries": "Serie",
+    "TabFavorites": "Favorites",
+    "TabMyLibrary": "My Library",
     "ButtonCancelRecording": "Aufnahme abbrechen",
     "HeaderPrePostPadding": "Pufferzeit vor\/nach der Aufnahme",
     "LabelPrePaddingMinutes": "Minuten vor der Aufnahme",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "Auto",
     "LiveTvPluginRequired": "Ein Live TV Serviceproviderplugin ist notwendig um fortzufahren.",
     "LiveTvPluginRequiredHelp": "Bitte installieren Sie eines der verf\u00fcgbaren Plugins, wie z.B. Next Pvr oder ServerWmc.",
-    "HeaderCustomizeOptionsPerMediaType": "Passe die Optionen per Medientyp an",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Men\u00fc",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Prim\u00e4r",
     "HeaderFetchImages": "Bilder abrufen:",
     "HeaderImageSettings": "Bild Einstellungen",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "Maximale Anzahl von Backdrops pro Element:",
     "LabelMaxScreenshotsPerItem": "Maximale Anzahl von Screenshots pro Element:",
     "LabelMinBackdropDownloadWidth": "Minimale Breite f\u00fcr zu herunterladende Backdrops:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "\u00dcbersprungen",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Serien:",
-    "LabelSeasonNumber": "Staffelnummer:",
-    "LabelEpisodeNumber": "Episodennummer:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "LabelSeasonNumber": "Staffelnummer",
+    "LabelEpisodeNumber": "Episodennummer",
+    "LabelEndingEpisodeNumber": "Ending episode number",
     "LabelEndingEpisodeNumberHelp": "Nur erforderlich f\u00fcr Mehrfachepisoden",
     "HeaderSupportTheTeam": "Unterst\u00fcze das Media Browser Team",
     "LabelSupportAmount": "Betrag (USD)",
@@ -529,8 +546,8 @@
     "ButtonRetrieveKey": "Schl\u00fcssel wiederherstellen",
     "LabelSupporterKey": "Unterst\u00fctzerschl\u00fcssel (einf\u00fcgen aus E-Mail)",
     "LabelSupporterKeyHelp": "Geben Sie ihren Unterst\u00fctzerschl\u00fcssel ein, um zus\u00e4tzliche Vorteile zugenie\u00dfen, die die Community f\u00fcr Media Browser entwickelt hat.",
-    "MessageInvalidKey": "MB3 Schl\u00fcssel nicht vorhanden oder ung\u00fcltig",
-    "ErrorMessageInvalidKey": "Um einen Premiuminhalt zu registrieren, m\u00fcssen sie ein MB3 Unters\u00fctzer sein. Bitte spenden sie und unterst\u00fctzen so die Weiterentwicklung des Kernprodukts. Danke.",
+    "MessageInvalidKey": "MB3 Schl\u00fcssel nicht vorhanden oder ung\u00fcltig.",
+    "ErrorMessageInvalidKey": "Um einen Premiuminhalt zu registrieren, m\u00fcssen sie auch ein MB3 Unters\u00fctzer sein. Bitte spenden sie und unterst\u00fctzen so die Weiterentwicklung des Kernprodukts. Danke.",
     "HeaderDisplaySettings": "Anzeige Einstellungen",
     "TabPlayTo": "Spiele an",
     "LabelEnableDlnaServer": "Aktiviere DLNA Server",
@@ -560,9 +577,12 @@
     "NotificationOptionPluginUninstalled": "Plugin deinstalliert",
     "NotificationOptionVideoPlayback": "Videowiedergabe",
     "NotificationOptionAudioPlayback": "Audiowiedergabe",
-    "NotificationOptionGamePlayback": "Game playback",
+    "NotificationOptionGamePlayback": "Game playback started",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionInstallationFailed": "Installation failure",
+    "NotificationOptionInstallationFailed": "Installationsfehler",
     "NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugef\u00fcgt",
     "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
     "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
@@ -576,26 +596,27 @@
     "CategorySystem": "System",
     "CategoryApplication": "Anwendung",
     "CategoryPlugin": "Plugin",
-    "LabelMessageTitle": "Message title:",
-    "LabelAvailableTokens": "Available tokens:",
-    "AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.",
+    "LabelMessageTitle": "Benachrichtigungstitel:",
+    "LabelAvailableTokens": "Verf\u00fcgbare Tokens:",
+    "AdditionalNotificationServices": "Durchsuchen Sie den Plugin Katalog, um weitere Benachrichtigungsservices zu installieren.",
     "OptionAllUsers": "Alle Benutzer",
     "OptionAdminUsers": "Administratoren",
-    "OptionCustomUsers": "Custom",
+    "OptionCustomUsers": "Benutzer",
     "ButtonArrowUp": "Auf",
     "ButtonArrowDown": "Ab",
     "ButtonArrowLeft": "Links",
     "ButtonArrowRight": "Rechts",
     "ButtonBack": "Zur\u00fcck",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Bild auf",
     "ButtonPageDown": "Bild ab",
     "PageAbbreviation": "PG",
     "ButtonHome": "Home",
     "ButtonSettings": "Einstellungen",
     "ButtonTakeScreenshot": "Bildschirmfoto aufnehmen",
-    "ButtonLetterUp": "Letter Up",
-    "ButtonLetterDown": "Letter Down",
+    "ButtonLetterUp": "Buchstabe hoch",
+    "ButtonLetterDown": "Buchstabe runter",
     "PageButtonAbbreviation": "PG",
     "LetterButtonAbbreviation": "A",
     "TabNowPlaying": "Aktuelle Wiedergabe",
@@ -610,30 +631,29 @@
     "ButtonStop": "Stop",
     "ButtonPause": "Pause",
     "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections",
-    "LabelGroupMoviesIntoCollectionsHelp": "When displaying movie lists, movies belonging to a collection will be displayed as one grouped item.",
+    "LabelGroupMoviesIntoCollectionsHelp": "Wenn Filmlisten angezeigt werden, dann werden Filme, die zu einer Collection geh\u00f6ren, als ein gruppiertes Element angezeigt.",
     "NotificationOptionPluginError": "Plugin Fehler",
     "ButtonVolumeUp": "Lauter",
     "ButtonVolumeDown": "Leiser",
     "ButtonMute": "Stumm",
-    "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
+    "HeaderLatestMedia": "Letzte Medien",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
-    "HeaderMyLibrary": "My Library",
-    "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:",
+    "HeaderMyLibrary": "Meine Bibliothek",
+    "LabelProfileCodecsHelp": "Getrennt durch Komma. Leerlassen, um auf alle Codecs anzuwenden.",
+    "LabelProfileContainersHelp": "Getrennt durch Komma. Leerlassen, um auf alle Container anzuwenden.",
+    "HeaderResponseProfile": "Antwort Profil",
+    "LabelType": "Typ:",
     "LabelProfileContainer": "Container:",
-    "LabelProfileVideoCodecs": "Video codecs:",
-    "LabelProfileAudioCodecs": "Audio codecs:",
+    "LabelProfileVideoCodecs": "Video Codecs:",
+    "LabelProfileAudioCodecs": "Audio Codecs:",
     "LabelProfileCodecs": "Codecs:",
-    "HeaderDirectPlayProfile": "Direct Play Profile",
-    "HeaderTranscodingProfile": "Transcoding Profile",
-    "HeaderCodecProfile": "Codec Profile",
-    "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.",
-    "HeaderContainerProfile": "Container Profile",
-    "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.",
+    "HeaderDirectPlayProfile": "Direktwiedergabe Profil",
+    "HeaderTranscodingProfile": "Transcoding Profil",
+    "HeaderCodecProfile": "Codec Profil",
+    "HeaderCodecProfileHelp": "Codec Profile weisen auf Beschr\u00e4nkungen eines Ger\u00e4tes beim Abspielen bestimmter Codecs hin. Wenn eine Beschr\u00e4nkung zutrifft, dann werden Medien transcodiert, auch wenn der Codec f\u00fcr die Direktwiedergabe konfiguriert ist.",
+    "HeaderContainerProfile": "Container Profil",
+    "HeaderContainerProfileHelp": "Container Profile weisen auf Beschr\u00e4nkungen einen Ger\u00e4tes beim Abspielen bestimmter Formate hin. Wenn eine Beschr\u00e4nkung zutrifft, dann werden Medien transcodiert, auch wenn das Format f\u00fcr die Direktwiedergabe konfiguriert ist.",
     "OptionProfileVideo": "Video",
     "OptionProfileAudio": "Audio",
     "OptionProfileVideoAudio": "Video Audio",
@@ -646,11 +666,11 @@
     "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",
+    "TabDirectPlay": "Direktwiedergabe",
+    "TabContainers": "Container",
     "TabCodecs": "Codecs",
     "TabResponses": "Responses",
-    "HeaderProfileInformation": "Profile Information",
+    "HeaderProfileInformation": "Profil Infomationen",
     "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:",
@@ -672,8 +692,8 @@
     "LabelFriendlyName": "Friendly name",
     "LabelManufacturer": "Manufacturer",
     "LabelManufacturerUrl": "Manufacturer url",
-    "LabelModelName": "Model name",
-    "LabelModelNumber": "Model number",
+    "LabelModelName": "Modellname",
+    "LabelModelNumber": "Modellnummer",
     "LabelModelDescription": "Model description",
     "LabelModelUrl": "Model url",
     "LabelSerialNumber": "Serial number",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/el.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Audio language preference:",
     "LabelSubtitleLanguagePreference": "Subtitle language preference:",
-    "LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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": "\u03c4\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb",
     "TabSecurity": "A\u03c3\u03c6\u03ac\u03bb\u03b5\u03b9\u03b1 ",
     "ButtonAddUser": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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",
@@ -270,8 +283,8 @@
     "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 - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customize options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/en_GB.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Audio language preference:",
     "LabelSubtitleLanguagePreference": "Subtitle language preference:",
-    "LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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 utilise:",
     "VersionNumber": "Version {0}",
     "TabPaths": "Paths",
@@ -270,8 +283,8 @@
     "ButtonAutoScroll": "Auto-scroll",
     "LabelImageSavingConvention": "Image saving convention:",
     "LabelImageSavingConventionHelp": "Media Browser recognises images from most major media applications. Choosing your downloading convention is useful if you also use other products.",
-    "OptionImageSavingCompatible": "Compatible - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customise options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 49 - 15
MediaBrowser.Server.Implementations/Localization/Server/en_US.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Playback Settings",
     "LabelAudioLanguagePreference": "Audio language preference:",
     "LabelSubtitleLanguagePreference": "Subtitle language preference:",
-    "LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Features:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subtitles",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Theme Song",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Search",
     "ButtonGroupVersions": "Group Versions",
     "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",
@@ -270,8 +283,8 @@
     "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 - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Sign In",
     "TitleSignIn": "Sign In",
     "HeaderPleaseSignIn": "Please sign in",
@@ -281,10 +294,13 @@
     "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:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Customize options per media type",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "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:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Skipped",
     "HeaderEpisodeOrganization": "Episode Organization",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "Season number:",
-    "LabelEpisodeNumber": "Episode number:",
-    "LabelEndingEpisodeNumber": "Ending episode number:",
+    "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)",
@@ -529,8 +546,8 @@
     "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": "MB3 Key Missing or Invalid",
-    "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
+    "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",
@@ -558,9 +575,12 @@
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback",
-    "NotificationOptionAudioPlayback": "Audio playback",
-    "NotificationOptionGamePlayback": "Game playback",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 39 - 5
MediaBrowser.Server.Implementations/Localization/Server/es.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Ajustes de reproducci\u00f3n",
     "LabelAudioLanguagePreference": "Preferencia de idioma de audio",
     "LabelSubtitleLanguagePreference": "Preferencia de idioma de subtitulos",
-    "LabelDisplayForcedSubtitlesOnly": "Mostrar \u00fanicamente subtitulos forzados",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "Sin subt\u00edtulos",
+    "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": "Perfiles",
     "TabSecurity": "Seguridad",
     "ButtonAddUser": "Agregar Usuario",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Caracter\u00edsticas",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "Subt\u00edtulos",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Banda sonora",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Buscar",
     "ButtonGroupVersions": "Versiones de Grupo",
     "PismoMessage": "Usando Pismo File Mount a trav\u00e9s de una licencia donada.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "Por favor apoye otros productos gratuitos que utilizamos:",
     "VersionNumber": "Versi\u00f3n {0}",
     "TabPaths": "Ruta",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "No se necesitan contrase\u00f1as al iniciar sesi\u00f3n desde localhost.",
     "TabGuide": "Gu\u00eda",
     "TabChannels": "Canales",
+    "TabCollections": "Collections",
     "HeaderChannels": "Canales",
     "TabRecordings": "Grabaciones",
     "TabScheduled": "Programado",
     "TabSeries": "Series",
+    "TabFavorites": "Favorites",
+    "TabMyLibrary": "My Library",
     "ButtonCancelRecording": "Cancelar grabaci\u00f3n",
     "HeaderPrePostPadding": "Pre\/post grabaci\u00f3n extra",
     "LabelPrePaddingMinutes": "Minutos previos extras:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Personalizar las opciones por tipo de medio",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Miniatura",
     "OptionDownloadMenuImage": "Men\u00fa",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Principal",
     "HeaderFetchImages": "Buscar im\u00e1genes",
     "HeaderImageSettings": "Opciones de im\u00e1gen",
+    "TabOther": "Other",
     "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:",
@@ -561,6 +578,9 @@
     "NotificationOptionVideoPlayback": "Reproducci\u00f3n de video",
     "NotificationOptionAudioPlayback": "Reproducci\u00f3n de audio",
     "NotificationOptionGamePlayback": "Iniciar juegos",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "La tarea programada ha fallado",
     "NotificationOptionInstallationFailed": "Fallo en la instalaci\u00f3n",
     "NotificationOptionNewLibraryContent": "Nuevo contenido a\u00f1adido",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Derecha",
     "ButtonBack": "Atr\u00e1s",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "P\u00e1gina arriba",
     "ButtonPageDown": "P\u00e1gina abajo",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Bajar volumen",
     "ButtonMute": "Silencio",
     "HeaderLatestMedia": "\u00daltimos medios",
-    "OptionNoSubtitles": "Sin subt\u00edtulos",
     "OptionSpecialFeatures": "Caracter\u00edsticas especiales",
     "HeaderCollections": "Colecciones",
     "HeaderMyLibrary": "Mi librer\u00eda",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subt\u00edtulos",
     "LabelOpenSubtitlesUsername": "Usuario de Open Subtitles:",
     "LabelOpenSubtitlesPassword": "Contrase\u00f1a de Open Subtitles:",
-    "LabelAudioLanguagePreferenceHelp": "Si est\u00e1 vac\u00edo, se seleccionar\u00e1 la pista de audio por defecto, sin importar el idioma.",
+    "LabelPlayDefaultAudioTrack": "Play default audio track regardless of language",
+    "LabelSubtitlePlaybackMode": "Subtitle mode:",
     "LabelDownloadLanguages": "Idiomas de descarga:",
     "ButtonRegister": "Registrar",
     "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 51 - 17
MediaBrowser.Server.Implementations/Localization/Server/es_MX.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Configuraci\u00f3n de Reproducci\u00f3n",
     "LabelAudioLanguagePreference": "Preferencia de idioma de audio:",
     "LabelSubtitleLanguagePreference": "Preferencia de idioma de subt\u00edtulos:",
-    "LabelDisplayForcedSubtitlesOnly": "Mostrar \u00fanicamente subtitulos forzados",
+    "OptionDefaultSubtitles": "Por Defecto",
+    "OptionOnlyForcedSubtitles": "\u00danicamente subt\u00edtulos forzados",
+    "OptionAlwaysPlaySubtitles": "Siempre mostrar subt\u00edtulos",
+    "OptionNoSubtitles": "Sin Subtitulos",
+    "OptionDefaultSubtitlesHelp": "Los subt\u00edtulos que coincidan con el lenguaje preferido ser\u00e1n cargados cuando el audio se encuentre en un lenguaje extrangero.",
+    "OptionOnlyForcedSubtitlesHelp": "Se cargar\u00e1n \u00fanicamente subt\u00edtulos marcados como forzados.",
+    "OptionAlwaysPlaySubtitlesHelp": "Los subt\u00edtulos que coincidan con el lenguaje preferido ser\u00e1n cargados independientemente del lenguaje del audio.",
+    "OptionNoSubtitlesHelp": "Los subt\u00edtulos no ser\u00e1n cargados por defecto.",
     "TabProfiles": "Perfiles",
     "TabSecurity": "Seguridad",
     "ButtonAddUser": "Agregar Usuario",
@@ -161,6 +168,10 @@
     "OptionIso": "ISO",
     "Option3D": "3D",
     "LabelFeatures": "Caracter\u00edsticas:",
+    "LabelService": "Servicio:",
+    "LabelStatus": "Estado:",
+    "LabelVersion": "Versi\u00f3n:",
+    "LabelLastResult": "\u00daltimo resultado:",
     "OptionHasSubtitles": "Subt\u00edtulos",
     "OptionHasTrailer": "Avance",
     "OptionHasThemeSong": "Canci\u00f3n del Tema",
@@ -224,6 +235,8 @@
     "ButtonSearch": "B\u00fasqueda",
     "ButtonGroupVersions": "Agrupar Versiones",
     "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",
     "PleaseSupportOtherProduces": "Por favor apoye otros productos libres que utilizamos:",
     "VersionNumber": "Versi\u00f3n {0}",
     "TabPaths": "Rutas",
@@ -270,8 +283,8 @@
     "ButtonAutoScroll": "Auto-desplazamiento",
     "LabelImageSavingConvention": "Convenci\u00f3n de almacenamiento de im\u00e1genes:",
     "LabelImageSavingConventionHelp": "MediaBrowser reconoce im\u00e1genes de las aplicaciones de medios m\u00e1s importantes. Seleccionar la convenci\u00f3n de descarga es \u00fatil si utiliza otros productos.",
-    "OptionImageSavingCompatible": "Compatible - MB3\/Plex\/Xbmc",
-    "OptionImageSavingStandard": "Est\u00e1ndar - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/Xbmc",
+    "OptionImageSavingStandard": "Est\u00e1ndar - MB2",
     "ButtonSignIn": "Iniciar Sesi\u00f3n",
     "TitleSignIn": "Iniciar Sesi\u00f3n",
     "HeaderPleaseSignIn": "Por favor inicie sesi\u00f3n",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "Las contrase\u00f1as no se requieren cuando se inicia sesi\u00f3n desde localhost.",
     "TabGuide": "Gu\u00eda",
     "TabChannels": "Canales",
+    "TabCollections": "Colecciones",
     "HeaderChannels": "Canales",
     "TabRecordings": "Grabaciones",
     "TabScheduled": "Programados",
     "TabSeries": "Series",
+    "TabFavorites": "Favoritos",
+    "TabMyLibrary": "Mi Biblioteca",
     "ButtonCancelRecording": "Cancelar Grabaci\u00f3n",
     "HeaderPrePostPadding": "Pre\/Post protecci\u00f3n",
     "LabelPrePaddingMinutes": "Minutos de protecci\u00f3n previos:",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "Auto",
     "LiveTvPluginRequired": "Se requiere de un complemento proveedor de servicios de TV en vivo para continuar.",
     "LiveTvPluginRequiredHelp": "Por favor instale alguno de los complementos disponibles, como Next PVR o ServerWMC.",
-    "HeaderCustomizeOptionsPerMediaType": "Personalice opciones por tipo de medio",
+    "LabelCustomizeOptionsPerMediaType": "Personalizar por tipo de medio:",
     "OptionDownloadThumbImage": "Miniatura",
     "OptionDownloadMenuImage": "Men\u00fa",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Principal",
     "HeaderFetchImages": "Buscar im\u00e1genes:",
     "HeaderImageSettings": "Opciones de Im\u00e1genes",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "N\u00famero m\u00e1ximo de im\u00e1genes de fondo por \u00edtem:",
     "LabelMaxScreenshotsPerItem": "N\u00famero m\u00e1ximo de capturas de pantalla por \u00edtem:",
     "LabelMinBackdropDownloadWidth": "Anchura m\u00ednima de descarga de im\u00e1genes de fondo:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Omitido",
     "HeaderEpisodeOrganization": "Organizaci\u00f3n de Episodios",
     "LabelSeries": "Series:",
-    "LabelSeasonNumber": "N\u00famero de temporada:",
-    "LabelEpisodeNumber": "N\u00famero de episodio:",
-    "LabelEndingEpisodeNumber": "N\u00famero episodio final:",
+    "LabelSeasonNumber": "N\u00famero de temporada",
+    "LabelEpisodeNumber": "N\u00famero de episodio",
+    "LabelEndingEpisodeNumber": "N\u00famero episodio final",
     "LabelEndingEpisodeNumberHelp": "S\u00f3lo requerido para archivos multi-episodio",
     "HeaderSupportTheTeam": "Apoye al Equipo de Media Browser",
     "LabelSupportAmount": "Importe (USD)",
@@ -529,8 +546,8 @@
     "ButtonRetrieveKey": "Recuperar Clave",
     "LabelSupporterKey": "Clave de Aficionado (pegue desde el correo electr\u00f3nico)",
     "LabelSupporterKeyHelp": "Introduzca su clave de aficionado para comenzar a disfrutar beneficios adicionales que la comunidad ha desarrollado para Media Browser.",
-    "MessageInvalidKey": "Falta Clave de MB3 o es Inv\u00e1lida",
-    "ErrorMessageInvalidKey": "Para que cualquier contenido Premium sea registrado, tambi\u00e9n debe ser un Aficionado MB3. Por favor done y ayude a continuar con el desarrollo del producto base. Gracias.",
+    "MessageInvalidKey": "Falta Clave de aficionado o es Inv\u00e1lida",
+    "ErrorMessageInvalidKey": "Para que cualquier contenido Premium sea registrado, tambi\u00e9n debe ser un Aficionado de Media Browser. Por favor done y ayude a continuar con el desarrollo del producto base. Gracias.",
     "HeaderDisplaySettings": "Configuraci\u00f3n de Pantalla",
     "TabPlayTo": "Reproducir En",
     "LabelEnableDlnaServer": "Habilitar servidor DLNA",
@@ -561,10 +578,13 @@
     "NotificationOptionVideoPlayback": "Reproducci\u00f3n de video",
     "NotificationOptionAudioPlayback": "Reproducci\u00f3n de audio",
     "NotificationOptionGamePlayback": "Ejecuci\u00f3n de juegos",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "Falla de tarea programada",
     "NotificationOptionInstallationFailed": "Falla de instalaci\u00f3n",
     "NotificationOptionNewLibraryContent": "Adici\u00f3n de nuevos contenidos",
-    "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
+    "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Derecha",
     "ButtonBack": "Atr\u00e1s",
     "ButtonInfo": "Info",
+    "ButtonOsd": "Visualizaci\u00f3n en pantalla",
     "ButtonPageUp": "P\u00e1gina arriba",
     "ButtonPageDown": "P\u00e1gina abajo",
     "PageAbbreviation": "Pag.",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Bajar Volumen",
     "ButtonMute": "Mudo",
     "HeaderLatestMedia": "\u00daltimos Medios",
-    "OptionNoSubtitles": "Sin Subtitulos",
     "OptionSpecialFeatures": "Caracter\u00edsticas Especiales",
     "HeaderCollections": "Colecciones",
     "HeaderMyLibrary": "Mi Biblioteca",
@@ -683,11 +703,11 @@
     "HeaderTranscodingProfileHelp": "A\u00f1ada 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 em el namespace urn:schemas-dlna-org:device-1-0.",
+    "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.",
     "LabelXDlnaDoc": "X-DLNA Doc:",
     "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.",
     "LabelSonyAggregationFlags": "Banderas de agregaci\u00f3n Sony:",
-    "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags in el namespace urn:schemas-sonycom:av",
+    "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el namespace urn:schemas-sonycom:av",
     "LabelTranscodingContainer": "Contenedor:",
     "LabelTranscodingVideoCodec": "Codec de video:",
     "LabelTranscodingVideoProfile": "Perfil de video:",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subt\u00edtulos",
     "LabelOpenSubtitlesUsername": "Nombre de usuario de Open Subtitles:",
     "LabelOpenSubtitlesPassword": "Contrase\u00f1a de Open Subtitles:",
-    "LabelAudioLanguagePreferenceHelp": "Si se deja vac\u00edo, la pista de audio por defecto ser\u00e1 seleccionada, independientemente del lenguaje.",
+    "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del lenguaje",
+    "LabelSubtitlePlaybackMode": "Modo de subt\u00edtulo:",
     "LabelDownloadLanguages": "Descargar lenguajes:",
     "ButtonRegister": "Registrar",
     "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el lenguaje de descarga",
     "LabelSkipIfAudioTrackPresentHelp": "Desactive esto para asegurar que todos los videos tengan subt\u00edtulos, independientemente del lenguaje del audio.",
-    "HeaderSendMessage": "Send Message",
-    "ButtonSend": "Send",
-    "LabelMessageText": "Message text:"
+    "HeaderSendMessage": "Enviar Mensaje",
+    "ButtonSend": "Enviar",
+    "LabelMessageText": "Texto del Mensaje:",
+    "MessageNoAvailablePlugins": "No hay complementos disponibles.",
+    "LabelDisplayPluginsFor": "Desplegar complementos para:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Otros",
+    "LabelEpisodeName": "Nombre del episodio",
+    "LabelSeriesName": "Nombre de la serie",
+    "ValueSeriesNamePeriod": "Nombre.serie",
+    "ValueSeriesNameUnderscore": "Nombre_serie",
+    "ValueEpisodeNamePeriod": "Nombre del episodio",
+    "ValueEpisodeNameUnderscore": "Nombre_episodio",
+    "HeaderTypeText": "Capturar Texto",
+    "LabelTypeText": "Texto"
 }

+ 47 - 13
MediaBrowser.Server.Implementations/Localization/Server/fr.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Param\u00e8tres de lecture",
     "LabelAudioLanguagePreference": "Param\u00e8tres de langue audio:",
     "LabelSubtitleLanguagePreference": "Param\u00e8tres de langue de sous-titre",
-    "LabelDisplayForcedSubtitlesOnly": "Afficher seulement les sous-titres forc\u00e9s",
+    "OptionDefaultSubtitles": "Par d\u00e9faut",
+    "OptionOnlyForcedSubtitles": "Seulement les sous-titres forc\u00e9s",
+    "OptionAlwaysPlaySubtitles": "Toujours afficher les sous-titres",
+    "OptionNoSubtitles": "Aucun sous-titre",
+    "OptionDefaultSubtitlesHelp": "Les sous-titres correspondants \u00e0 la langue pr\u00e9f\u00e9r\u00e9e seront charg\u00e9s lorsque la langue audio est \u00e9trang\u00e8re.",
+    "OptionOnlyForcedSubtitlesHelp": "Seulement les sous-titres forc\u00e9s seront charg\u00e9s.",
+    "OptionAlwaysPlaySubtitlesHelp": "Les sous-titres correspondants \u00e0 la langue pr\u00e9f\u00e9r\u00e9e seront charg\u00e9s peu importe la langue audio.",
+    "OptionNoSubtitlesHelp": "Les sous-titres ne seront pas charg\u00e9s par d\u00e9faut.",
     "TabProfiles": "Profils",
     "TabSecurity": "S\u00e9curit\u00e9",
     "ButtonAddUser": "Ajouter utilisateur",
@@ -161,6 +168,10 @@
     "OptionIso": "ISO",
     "Option3D": "3D",
     "LabelFeatures": "Caract\u00e9ristiques:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Dernier r\u00e9sultat:",
     "OptionHasSubtitles": "Sous-titres",
     "OptionHasTrailer": "Bande-annnonce",
     "OptionHasThemeSong": "Chanson th\u00e8me",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Recherche",
     "ButtonGroupVersions": "Versions des groupes",
     "PismoMessage": "En utilisation de \"Pismo File Mount\" par une license fournie.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "SVP, soutenez les autres produits gratuits que nous utilisons:",
     "VersionNumber": "Version {0}",
     "TabPaths": "Chemins d'acc\u00e8s",
@@ -270,8 +283,8 @@
     "ButtonAutoScroll": "D\u00e9fillement automatique",
     "LabelImageSavingConvention": "Convention de sauvegarde des images:",
     "LabelImageSavingConventionHelp": "Media Browser reconnait les images des autres applications de m\u00e9dia importants. Choisir la convention de t\u00e9l\u00e9chargement peut \u00eatre pratique si vous utilisez aussi d'autres produits.",
-    "OptionImageSavingCompatible": "Compatible - MB3\/Plex\/XBMC",
-    "OptionImageSavingStandard": "Standard - MB3\/MB2",
+    "OptionImageSavingCompatible": "Compatible - Media Browser\/Plex\/XBMC",
+    "OptionImageSavingStandard": "Standard - MB2",
     "ButtonSignIn": "Se connecter",
     "TitleSignIn": "Se connecter",
     "HeaderPleaseSignIn": "SVP se connecter",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "Aucun mot de passe requis pour les connexions par \"localhost\".",
     "TabGuide": "Guide horaire",
     "TabChannels": "Cha\u00eenes",
+    "TabCollections": "Collections",
     "HeaderChannels": "Cha\u00eenes",
     "TabRecordings": "Enregistrements",
     "TabScheduled": "Programm\u00e9s",
     "TabSeries": "S\u00e9ries",
+    "TabFavorites": "Favoris",
+    "TabMyLibrary": "Ma Biblioth\u00e8que",
     "ButtonCancelRecording": "Annuler l'enregistrement",
     "HeaderPrePostPadding": "Pr\u00e9-remplissage",
     "LabelPrePaddingMinutes": "Minutes de Pr\u00e9-remplissage:",
@@ -319,7 +335,7 @@
     "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.",
-    "HeaderCustomizeOptionsPerMediaType": "Personnaliser les param\u00e8tres par type de m\u00e9dias",
+    "LabelCustomizeOptionsPerMediaType": "Personnaliser pour le type de m\u00e9dia:",
     "OptionDownloadThumbImage": "Vignette",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Principal",
     "HeaderFetchImages": "T\u00e9l\u00e9charger Images:",
     "HeaderImageSettings": "Param\u00e8tres d'image",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "Nombre maximum d'images d'arri\u00e8re-plan par item:",
     "LabelMaxScreenshotsPerItem": "Nombre maximum de captures d'\u00e9cran par item:",
     "LabelMinBackdropDownloadWidth": "Largeur minimum d'image d'arri\u00e8re-plan \u00e0 t\u00e9l\u00e9charger:",
@@ -463,9 +480,9 @@
     "LabelSkipped": "Saut\u00e9",
     "HeaderEpisodeOrganization": "Organisation d'\u00e9pisodes",
     "LabelSeries": "S\u00e9ries:",
-    "LabelSeasonNumber": "Num\u00e9ro de saison:",
+    "LabelSeasonNumber": "Num\u00e9ro de saison",
     "LabelEpisodeNumber": "Num\u00e9ro d'\u00e9pisode",
-    "LabelEndingEpisodeNumber": "Num\u00e9ro d'\u00e9pisode se terminant:",
+    "LabelEndingEpisodeNumber": "Num\u00e9ro d'\u00e9pisode se terminant",
     "LabelEndingEpisodeNumberHelp": "Seulement requis pour les fichiers multi-\u00e9pisodes",
     "HeaderSupportTheTeam": "Soutenez l'\u00e9quippe Media Browser",
     "LabelSupportAmount": "Montant (USD)",
@@ -529,8 +546,8 @@
     "ButtonRetrieveKey": "obtenir la cl\u00e9",
     "LabelSupporterKey": "Cl\u00e9 de supporteur (coller du courriel)",
     "LabelSupporterKeyHelp": "Entrez votre cl\u00e9 du supporteur pour commencer \u00e0 profiter des b\u00e9n\u00e9fices additionnels que la communaut\u00e9 a d\u00e9velopp\u00e9 pour Media Browser.",
-    "MessageInvalidKey": "Cl\u00e9 MB3 manquante ou invalide",
-    "ErrorMessageInvalidKey": "Pour que le contenu premium soit enregistr\u00e9, vous devez aussi \u00eatre supporteur MB3. S'il vous plait effectuez des dons et soutenez la continuation du d\u00e9veloppement de Media Browser.",
+    "MessageInvalidKey": "Cl\u00e9 de supporteur manquante ou invalide",
+    "ErrorMessageInvalidKey": "Pour que le contenu premium soit enregistr\u00e9, vous devez aussi \u00eatre supporteur Media Browser. S'il vous plait, effectuez des dons et soutenez la continuation du d\u00e9veloppement de Media Browser.",
     "HeaderDisplaySettings": "Param\u00e8tres d'affichage",
     "TabPlayTo": "Lire sur",
     "LabelEnableDlnaServer": "Activer le serveur DLNA",
@@ -561,6 +578,9 @@
     "NotificationOptionVideoPlayback": "Lecture vid\u00e9o",
     "NotificationOptionAudioPlayback": "Lecture audio",
     "NotificationOptionGamePlayback": "Lecture des jeux",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "\u00c9chec de t\u00e2che programm\u00e9e",
     "NotificationOptionInstallationFailed": "\u00c9chec d'installation",
     "NotificationOptionNewLibraryContent": "Nouveau contenu ajout\u00e9",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Droite",
     "ButtonBack": "Retour arri\u00e8re",
     "ButtonInfo": "Info",
+    "ButtonOsd": "Affichage \u00e0 l'\u00e9cran",
     "ButtonPageUp": "Page suivante",
     "ButtonPageDown": "Page pr\u00e9c\u00e9dante",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume bas",
     "ButtonMute": "Sourdine",
     "HeaderLatestMedia": "Derniers m\u00e9dias",
-    "OptionNoSubtitles": "Aucun sous-titre",
     "OptionSpecialFeatures": "\u00c9v\u00eanements sp\u00e9ciaux",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "Ma biblioth\u00e8que",
@@ -699,17 +719,31 @@
     "OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.",
     "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:",
-    "LabelSkipIfGraphicalSubsPresent": "Sauter le vid\u00e9o contient d\u00e9j\u00e0 des sous-titres graphiques",
+    "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:",
     "LabelOpenSubtitlesUsername": "Nom d'utilisateur de Open Subtitles:",
     "LabelOpenSubtitlesPassword": "Mot de passe de Open Subtitles:",
-    "LabelAudioLanguagePreferenceHelp": "Si laiss\u00e9 vide, la piste audio par d\u00e9faut sera s\u00e9lectionn\u00e9e, peu importe la langue.",
-    "LabelDownloadLanguages": "Langes de t\u00e9l\u00e9chargement:",
+    "LabelPlayDefaultAudioTrack": "Utiliser la flux audio par d\u00e9faut peu importe la langue",
+    "LabelSubtitlePlaybackMode": "Mode de sous-titres:",
+    "LabelDownloadLanguages": "T\u00e9l\u00e9chargement de langues:",
     "ButtonRegister": "S'enregistrer",
     "LabelSkipIfAudioTrackPresent": "Sauter si la piste audio correspond \u00e0 la langue de t\u00e9l\u00e9chargement",
     "LabelSkipIfAudioTrackPresentHelp": "D\u00e9cocher cette option va assurer que tous les vid\u00e9os ont des sous-titres, peu importe la langue audio.",
     "HeaderSendMessage": "Envoyer message",
     "ButtonSend": "Envoyer",
-    "LabelMessageText": "Texte du message:"
+    "LabelMessageText": "Texte du message:",
+    "MessageNoAvailablePlugins": "Aucun plugin disponible.",
+    "LabelDisplayPluginsFor": "Afficher les plugins pour:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theatre",
+    "TabOtherPlugins": "Autres",
+    "LabelEpisodeName": "Nom d'\u00e9pisode",
+    "LabelSeriesName": "Nom de s\u00e9ries",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Entrer texte",
+    "LabelTypeText": "Texte"
 }

+ 39 - 5
MediaBrowser.Server.Implementations/Localization/Server/he.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e0\u05d9\u05d2\u05d5\u05df",
     "LabelAudioLanguagePreference": "\u05e9\u05e4\u05ea \u05e7\u05d5\u05dc \u05de\u05d5\u05e2\u05d3\u05e4\u05ea:",
     "LabelSubtitleLanguagePreference": "\u05e9\u05e4\u05ea \u05db\u05ea\u05d5\u05d1\u05d9\u05d5\u05ea \u05de\u05d5\u05e2\u05d3\u05e4\u05ea:",
-    "LabelDisplayForcedSubtitlesOnly": "\u05d4\u05e6\u05d2 \u05e8\u05e7 \u05db\u05ea\u05d5\u05d1\u05d9\u05d5\u05ea \u05de\u05d0\u05d5\u05dc\u05e6\u05d5\u05ea",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "No Subtitles",
+    "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": "\u05e4\u05e8\u05d5\u05e4\u05d9\u05dc\u05d9\u05dd",
     "TabSecurity": "\u05d1\u05d8\u05d9\u05d7\u05d5\u05ea",
     "ButtonAddUser": "\u05d4\u05d5\u05e1\u05e3 \u05de\u05e9\u05ea\u05de\u05e9",
@@ -161,6 +168,10 @@
     "OptionIso": "ISO",
     "Option3D": "\u05ea\u05dc\u05ea \u05de\u05d9\u05de\u05d3",
     "LabelFeatures": "\u05de\u05d0\u05e4\u05d9\u05d9\u05e0\u05d9\u05dd:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "\u05db\u05ea\u05d5\u05d1\u05d9\u05d5\u05ea",
     "OptionHasTrailer": "\u05d8\u05e8\u05d9\u05d9\u05dc\u05e8",
     "OptionHasThemeSong": "\u05e9\u05d9\u05e8 \u05e0\u05d5\u05e9\u05d0",
@@ -224,6 +235,8 @@
     "ButtonSearch": "\u05d7\u05d9\u05e4\u05d5\u05e9",
     "ButtonGroupVersions": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05d2\u05e8\u05e1\u05d0\u05d5\u05ea",
     "PismoMessage": "\u05d0\u05e4\u05e9\u05e8 \u05d8\u05e2\u05d9\u05e0\u05ea \u05e7\u05d1\u05e6\u05d9 Pismo \u05d3\u05e8\u05da \u05e8\u05d9\u05e9\u05d9\u05d5\u05df \u05ea\u05e8\u05d5\u05de\u05d4.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "\u05d0\u05e0\u05d0 \u05ea\u05de\u05db\u05d5 \u05d1\u05e9\u05d9\u05e8\u05d5\u05ea\u05d9\u05dd \u05d7\u05d9\u05e0\u05de\u05d9\u05d9\u05dd \u05d0\u05d7\u05e8\u05d9\u05dd \u05e9\u05d1\u05d4\u05dd \u05d0\u05e0\u05d5 \u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd:",
     "VersionNumber": "\u05d2\u05d9\u05e8\u05e1\u05d0 {0}",
     "TabPaths": "\u05e0\u05ea\u05d9\u05d1\u05d9\u05dd",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "\u05d0\u05d9\u05df \u05e6\u05d5\u05e8\u05da \u05d1\u05e1\u05d9\u05e1\u05de\u05d0 \u05db\u05d0\u05e9\u05e8 \u05de\u05ea\u05d7\u05d1\u05e8\u05d9\u05dd \u05de\u05d4\u05e9\u05e8\u05ea \u05d4\u05de\u05e7\u05d5\u05de\u05d9.",
     "TabGuide": "\u05de\u05d3\u05e8\u05d9\u05da",
     "TabChannels": "\u05e2\u05e8\u05d5\u05e6\u05d9\u05dd",
+    "TabCollections": "Collections",
     "HeaderChannels": "\u05e2\u05e8\u05d5\u05e6\u05d9\u05dd",
     "TabRecordings": "\u05d4\u05e7\u05dc\u05d8\u05d5\u05ea",
     "TabScheduled": "\u05dc\u05d5\u05d7 \u05d6\u05de\u05e0\u05d9\u05dd",
     "TabSeries": "\u05e1\u05d3\u05e8\u05d5\u05ea",
+    "TabFavorites": "Favorites",
+    "TabMyLibrary": "My Library",
     "ButtonCancelRecording": "\u05d1\u05d8\u05dc \u05d4\u05e7\u05dc\u05d8\u05d4",
     "HeaderPrePostPadding": "\u05de\u05e8\u05d5\u05d5\u05d7 \u05de\u05e7\u05d3\u05d9\u05dd\/\u05de\u05d0\u05d5\u05d7\u05e8",
     "LabelPrePaddingMinutes": "\u05d3\u05e7\u05d5\u05ea \u05e9\u05dc \u05de\u05e8\u05d5\u05d5\u05d7 \u05de\u05e7\u05d3\u05d9\u05dd:",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "\u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9",
     "LiveTvPluginRequired": "\u05d9\u05e9 \u05e6\u05d5\u05e8\u05da \u05d1\u05ea\u05d5\u05e1\u05e3 \u05e1\u05e4\u05e7 \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d4 \u05d7\u05d9\u05d9\u05d4 \u05e2\u05dc \u05de\u05e0\u05ea \u05dc\u05d4\u05de\u05e9\u05d9\u05da.",
     "LiveTvPluginRequiredHelp": "\u05d0\u05e0\u05d0 \u05d4\u05ea\u05e7\u05df \u05d0\u05ea \u05d0\u05d7\u05d3 \u05de\u05d4\u05ea\u05d5\u05e1\u05e4\u05d9\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d9\u05d9\u05dd \u05e9\u05dc\u05e0\u05d5\u05ea \u05db\u05de\u05d5 Next Pvr \u05d0\u05d5 ServerWmc.",
-    "HeaderCustomizeOptionsPerMediaType": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d4\u05ea\u05d0\u05de\u05d4 \u05dc\u05e4\u05d9 \u05e1\u05d5\u05d2 \u05de\u05d3\u05d9\u05d4",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "Thumb",
     "OptionDownloadMenuImage": "\u05ea\u05e4\u05e8\u05d9\u05d8",
     "OptionDownloadLogoImage": "\u05dc\u05d5\u05d2\u05d5",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "\u05e8\u05d0\u05e9\u05d9",
     "HeaderFetchImages": "\u05d4\u05d1\u05d0 \u05ea\u05de\u05d5\u05e0\u05d5\u05ea:",
     "HeaderImageSettings": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05ea\u05de\u05d5\u05e0\u05d4",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "\u05de\u05e1\u05e4\u05e8 \u05ea\u05de\u05d5\u05e0\u05d5\u05ea \u05e8\u05e7\u05e2 \u05de\u05e7\u05e1\u05d9\u05de\u05d0\u05dc\u05d9 \u05dc\u05e4\u05e8\u05d9\u05d8:",
     "LabelMaxScreenshotsPerItem": "\u05de\u05e1\u05e4\u05e8 \u05ea\u05de\u05d5\u05e0\u05d5\u05ea \u05de\u05e1\u05da \u05de\u05e7\u05e1\u05d9\u05de\u05d0\u05dc\u05d9 \u05dc\u05e4\u05e8\u05d9\u05d8:",
     "LabelMinBackdropDownloadWidth": "\u05e8\u05d5\u05d7\u05d1 \u05ea\u05de\u05d5\u05e0\u05ea \u05e8\u05e7\u05e2 \u05de\u05d9\u05e0\u05d9\u05de\u05d0\u05dc\u05d9 \u05dc\u05d4\u05d5\u05e8\u05d3\u05d4:",
@@ -561,6 +578,9 @@
     "NotificationOptionVideoPlayback": "\u05e0\u05d9\u05d2\u05d5\u05df \u05d5\u05d5\u05d9\u05d3\u05d0\u05d5",
     "NotificationOptionAudioPlayback": "\u05e0\u05d9\u05d2\u05d5\u05df \u05d0\u05d5\u05d3\u05d9\u05d5",
     "NotificationOptionGamePlayback": "\u05d4\u05e6\u05d2\u05ea \u05de\u05e9\u05d7\u05e7",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "\u05de\u05e9\u05d9\u05de\u05d4 \u05de\u05ea\u05d5\u05d6\u05de\u05e0\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
     "NotificationOptionInstallationFailed": "\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e0\u05db\u05e9\u05dc\u05d4",
     "NotificationOptionNewLibraryContent": "\u05ea\u05d5\u05db\u05df \u05d7\u05d3\u05e9 \u05e0\u05d5\u05e1\u05e3",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Right",
     "ButtonBack": "Back",
     "ButtonInfo": "Info",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "Page Up",
     "ButtonPageDown": "Page Down",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Volume down",
     "ButtonMute": "Mute",
     "HeaderLatestMedia": "Latest Media",
-    "OptionNoSubtitles": "No Subtitles",
     "OptionSpecialFeatures": "Special Features",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

+ 53 - 19
MediaBrowser.Server.Implementations/Localization/Server/it.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "Impostazioni di riproduzione",
     "LabelAudioLanguagePreference": "Audio preferenze di lingua:",
     "LabelSubtitleLanguagePreference": "Sottotitoli preferenze di lingua:",
-    "LabelDisplayForcedSubtitlesOnly": "Visualizzare solo i sottotitoli forzati",
+    "OptionDefaultSubtitles": "Predefinito",
+    "OptionOnlyForcedSubtitles": "Solo i sottotitoli forzati",
+    "OptionAlwaysPlaySubtitles": "Visualizza sempre i sottotitoli",
+    "OptionNoSubtitles": "Nessun Sottotitolo",
+    "OptionDefaultSubtitlesHelp": "Sottotitoli corrispondenti alla lingua di preferenza saranno caricati quando l'audio \u00e8 in una lingua straniera.",
+    "OptionOnlyForcedSubtitlesHelp": "Solo sottotitoli contrassegnati come forzati saranno caricati.",
+    "OptionAlwaysPlaySubtitlesHelp": "Sottotitoli corrispondenti alla lingua di preferenza saranno caricati a prescindere dalla lingua audio.",
+    "OptionNoSubtitlesHelp": "I sottotitoli non verranno caricati di default.",
     "TabProfiles": "Profili",
     "TabSecurity": "Sicurezza",
     "ButtonAddUser": "Aggiungi Utente",
@@ -161,6 +168,10 @@
     "OptionIso": "Iso",
     "Option3D": "3D",
     "LabelFeatures": "Caratteristiche:",
+    "LabelService": "Servizio:",
+    "LabelStatus": "Stato:",
+    "LabelVersion": "Versione:",
+    "LabelLastResult": "Ultimo risultato:",
     "OptionHasSubtitles": "Sottotitoli",
     "OptionHasTrailer": "Trailer",
     "OptionHasThemeSong": "Tema Canzone",
@@ -224,6 +235,8 @@
     "ButtonSearch": "Cerca",
     "ButtonGroupVersions": "Versione Gruppo",
     "PismoMessage": "Dona per avere una licenza di Pismo",
+    "TangibleSoftwareMessage": "Utilizzando materiali Solutions convertitori Java \/ C # attraverso una licenza dopo aver donato.",
+    "HeaderCredits": "Crediti",
     "PleaseSupportOtherProduces": "Per favore supporta gli altri prodotti 'GRATIS' che MB utilizza",
     "VersionNumber": "Versione {0}",
     "TabPaths": "Percorso",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "Le password non sono richieste quando l'accesso e fatto da questo pc.",
     "TabGuide": "Guida",
     "TabChannels": "Canali",
+    "TabCollections": "Collezioni",
     "HeaderChannels": "Canali",
     "TabRecordings": "Registrazioni",
     "TabScheduled": "Pianificato",
     "TabSeries": "Serie TV",
+    "TabFavorites": "Preferiti",
+    "TabMyLibrary": "Mia Libreria",
     "ButtonCancelRecording": "Annulla la registrazione",
     "HeaderPrePostPadding": "Pre\/Post Registrazione",
     "LabelPrePaddingMinutes": "Pre registrazione minuti",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "Automatico",
     "LiveTvPluginRequired": "\u00e8 richiesto il servizio LIVE TV per continuare.",
     "LiveTvPluginRequiredHelp": "Installa un servizio disponibile, come Next Pvr or ServerWMC.",
-    "HeaderCustomizeOptionsPerMediaType": "Personalizza le opzioni per i media.",
+    "LabelCustomizeOptionsPerMediaType": "Personalizza per il tipo di supporto:",
     "OptionDownloadThumbImage": "Foto",
     "OptionDownloadMenuImage": "Menu",
     "OptionDownloadLogoImage": "Logo",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "Locandina",
     "HeaderFetchImages": "Identifica Immagini:",
     "HeaderImageSettings": "Impostazioni Immagini",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "Massimo numero di sfondi per oggetto.",
     "LabelMaxScreenshotsPerItem": "Massimo numero di foto per oggetto:",
     "LabelMinBackdropDownloadWidth": "Massima larghezza sfondo:",
@@ -561,10 +578,13 @@
     "NotificationOptionVideoPlayback": "Riproduzione video",
     "NotificationOptionAudioPlayback": "Riproduzione Audio",
     "NotificationOptionGamePlayback": "Riproduzione gioco",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "Fallimento operazione pianificata",
     "NotificationOptionInstallationFailed": "errore di installazione",
     "NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto",
-    "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
+    "NotificationOptionNewLibraryContentMultiple": "Nuovi contenuti aggiunti",
     "SendNotificationHelp": "Per impostazione predefinita, le notifiche vengono consegnate al cruscotto della Posta in arrivo . Sfoglia il catalogo plugin da installare opzioni di notifica aggiuntive.",
     "NotificationOptionServerRestartRequired": "Riavvio del server necessaria",
     "LabelNotificationEnabled": "Abilita questa notifica",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "Destra",
     "ButtonBack": "Indietro",
     "ButtonInfo": "Info",
+    "ButtonOsd": "Su Schermo",
     "ButtonPageUp": "Pagina Su",
     "ButtonPageDown": "Pagina Gi\u00f9",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "ButtonVolumeDown": "Diminuisci volume",
     "ButtonMute": "Muto",
     "HeaderLatestMedia": "Ultimi Media",
-    "OptionNoSubtitles": "Nessun Sottotitolo",
     "OptionSpecialFeatures": "caratteristiche Speciali",
     "HeaderCollections": "Collezioni",
     "HeaderMyLibrary": "Mia Libereria",
@@ -697,19 +717,33 @@
     "OptionEstimateContentLength": "Stimare la lunghezza contenuto quando transcodifica",
     "OptionReportByteRangeSeekingWhenTranscoding": "Segnala che il server supporta la ricerca di byte quando transcodifica",
     "OptionReportByteRangeSeekingWhenTranscodingHelp": "Questo \u00e8 necessario per alcuni dispositivi che il tempo non cercano molto bene.",
-    "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:",
-    "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",
-    "LabelOpenSubtitlesUsername": "Open Subtitles username:",
-    "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
-    "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:"
+    "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:",
+    "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",
+    "LabelOpenSubtitlesUsername": "Sottotitoli utente:",
+    "LabelOpenSubtitlesPassword": "Sottotitoli password:",
+    "LabelPlayDefaultAudioTrack": "Riprodurre la traccia audio di default indipendentemente dalla lingua",
+    "LabelSubtitlePlaybackMode": "Modalit\u00e0 Sottotitolo:",
+    "LabelDownloadLanguages": "Scarica lingue:",
+    "ButtonRegister": "registro",
+    "LabelSkipIfAudioTrackPresent": "Ignora se la traccia audio di default corrisponde alla lingua di download",
+    "LabelSkipIfAudioTrackPresentHelp": "Deselezionare questa opzione per assicurare che tutti i video hanno i sottotitoli, a prescindere dalla lingua audio.",
+    "HeaderSendMessage": "Invia un messaggio",
+    "ButtonSend": "Invia",
+    "LabelMessageText": "Testo del messaggio:",
+    "MessageNoAvailablePlugins": "Nessun plugin disponibili.",
+    "LabelDisplayPluginsFor": "Mostra plugin per:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Altri",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Nome serie",
+    "ValueSeriesNamePeriod": "Nome Serie",
+    "ValueSeriesNameUnderscore": "Nome Serie",
+    "ValueEpisodeNamePeriod": "Nome Episodio",
+    "ValueEpisodeNameUnderscore": "Nome Episodio",
+    "HeaderTypeText": "Inserisci il testo",
+    "LabelTypeText": "Testo"
 }

+ 39 - 5
MediaBrowser.Server.Implementations/Localization/Server/kk.json

@@ -63,7 +63,14 @@
     "HeaderPlaybackSettings": "\u041e\u0439\u043d\u0430\u0442\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456",
     "LabelAudioLanguagePreference": "\u0414\u044b\u0431\u044b\u0441 \u0442\u0456\u043b\u0456\u043d\u0456\u04a3 \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0456:",
     "LabelSubtitleLanguagePreference": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440 \u0442\u0456\u043b\u0456\u043d\u0456\u04a3 \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0456:",
-    "LabelDisplayForcedSubtitlesOnly": "\u0422\u0435\u043a \u049b\u0430\u043d\u0430 \u043c\u04d9\u0436\u0431\u04af\u0440\u043b\u0456 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440\u0434\u0456 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
+    "OptionDefaultSubtitles": "Default",
+    "OptionOnlyForcedSubtitles": "Only forced subtitles",
+    "OptionAlwaysPlaySubtitles": "Always play subtitles",
+    "OptionNoSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440 \u0436\u043e\u049b",
+    "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": "\u041f\u0440\u043e\u0444\u0438\u043b\u044c\u0434\u0435\u0440",
     "TabSecurity": "\u049a\u0430\u0443\u0456\u043f\u0441\u0456\u0437\u0434\u0456\u043a",
     "ButtonAddUser": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043d\u044b \u04af\u0441\u0442\u0435\u0443",
@@ -161,6 +168,10 @@
     "OptionIso": "ISO",
     "Option3D": "3D",
     "LabelFeatures": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440:",
+    "LabelService": "Service:",
+    "LabelStatus": "Status:",
+    "LabelVersion": "Version:",
+    "LabelLastResult": "Last result:",
     "OptionHasSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440",
     "OptionHasTrailer": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440",
     "OptionHasThemeSong": "\u0422\u0430\u049b\u044b\u0440\u044b\u043f \u04d9\u043d\u0456",
@@ -224,6 +235,8 @@
     "ButtonSearch": "\u0406\u0437\u0434\u0435\u0443",
     "ButtonGroupVersions": "\u041d\u04b1\u0441\u049b\u0430\u043b\u0430\u0440\u0434\u044b \u0442\u043e\u043f\u0442\u0430\u0443",
     "PismoMessage": "\u0421\u044b\u0439\u043b\u0430\u043d\u0493\u0430\u043d \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u044f \u0430\u0440\u049b\u044b\u043b\u044b Pismo File Mount \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0434\u0430.",
+    "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java\/C# converters through a donated license.",
+    "HeaderCredits": "Credits",
     "PleaseSupportOtherProduces": "\u0411\u0456\u0437 \u049b\u043e\u043b\u0434\u0430\u043d\u0430\u0442\u044b\u043d \u0431\u0430\u0441\u049b\u0430 \u0430\u0448\u044b\u049b \u04e9\u043d\u0456\u043c\u0434\u0435\u0440\u0434\u0456 \u049b\u043e\u043b\u0434\u0430\u04a3\u044b\u0437:",
     "VersionNumber": "\u041d\u04b1\u0441\u049b\u0430\u0441\u044b: {0}",
     "TabPaths": "\u0416\u043e\u043b\u0434\u0430\u0440",
@@ -281,10 +294,13 @@
     "PasswordLocalhostMessage": "\u0416\u0435\u0440\u0433\u0456\u043b\u0456\u043a\u0442\u0456 (localhost) \u043e\u0440\u044b\u043d\u0434\u0430\u043d \u043a\u0456\u0440\u0433\u0435\u043d\u0434\u0435 \u049b\u04b1\u043f\u0438\u044f \u0441\u04e9\u0437\u0434\u0435\u0440 \u049b\u0430\u0436\u0435\u0442 \u0435\u043c\u0435\u0441.",
     "TabGuide": "\u0422\u0414 \u043a\u0435\u0441\u0442\u0435\u0441\u0456",
     "TabChannels": "\u0422\u0435\u043b\u0435\u0430\u0440\u043d\u0430\u043b\u0430\u0440",
+    "TabCollections": "Collections",
     "HeaderChannels": "\u0422\u0435\u043b\u0435\u0430\u0440\u043d\u0430\u043b\u0430\u0440",
     "TabRecordings": "\u0416\u0430\u0437\u0431\u0430\u043b\u0430\u0440",
     "TabScheduled": "\u0416\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d",
     "TabSeries": "\u0421\u0435\u0440\u0438\u0430\u043b",
+    "TabFavorites": "Favorites",
+    "TabMyLibrary": "My Library",
     "ButtonCancelRecording": "\u0416\u0430\u0437\u0443\u0434\u044b \u0431\u043e\u043b\u0434\u044b\u0440\u043c\u0430\u0443",
     "HeaderPrePostPadding": "\u0410\u043b\u0493\u0430\/\u0410\u0440\u0442\u049b\u0430 \u0448\u0435\u0433\u0456\u043d\u0456\u0441",
     "LabelPrePaddingMinutes": "\u0410\u043b\u0493\u0430 \u0448\u0435\u0433\u0456\u043d\u0456\u0441, \u043c\u0438\u043d:",
@@ -319,7 +335,7 @@
     "OptionAutomatic": "\u0410\u0432\u0442\u043e\u0442\u0430\u04a3\u0434\u0430\u0443",
     "LiveTvPluginRequired": "\u0416\u0430\u043b\u0493\u0430\u0441\u0442\u044b\u0440\u0443 \u04af\u0448\u0456\u043d \u044d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414 \u049b\u044b\u0437\u043c\u0435\u0442\u0456\u043d \u0436\u0435\u0442\u043a\u0456\u0437\u0443\u0448\u0456\u0441\u0456 \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0456 \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.",
     "LiveTvPluginRequiredHelp": "\u0411\u0456\u0437\u0434\u0456\u04a3 \u049b\u043e\u043b \u0436\u0435\u0442\u0456\u043c\u0434\u0456 (Next Pvr \u043d\u0435 ServerWmc \u0441\u0438\u044f\u049b\u0442\u044b) \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440\u0434\u0456\u04a3 \u0431\u0456\u0440\u0435\u0443\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.",
-    "HeaderCustomizeOptionsPerMediaType": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0434\u0456 \u0442\u0430\u0441\u0443\u0448\u044b \u0442\u04af\u0440\u0456 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u0443",
+    "LabelCustomizeOptionsPerMediaType": "Customize for media type:",
     "OptionDownloadThumbImage": "\u041d\u043e\u0431\u0430\u0439",
     "OptionDownloadMenuImage": "\u041c\u04d9\u0437\u0456\u0440",
     "OptionDownloadLogoImage": "\u041b\u043e\u0433\u043e\u0442\u0438\u043f",
@@ -331,6 +347,7 @@
     "OptionDownloadPrimaryImage": "\u0411\u0430\u0441\u0442\u0430\u043f\u049b\u044b",
     "HeaderFetchImages": "\u0421\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0434\u0456 \u0448\u044b\u0493\u0430\u0440\u044b\u043f \u0430\u043b\u0443:",
     "HeaderImageSettings": "\u0421\u0443\u0440\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456",
+    "TabOther": "Other",
     "LabelMaxBackdropsPerItem": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0430\u0440\u0442\u049b\u044b \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0434\u0456\u04a3 \u0435\u04a3 \u043a\u04e9\u043f \u0441\u0430\u043d\u044b:",
     "LabelMaxScreenshotsPerItem": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0435\u04a3 \u043a\u04e9\u043f \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0441\u0430\u043d\u044b:",
     "LabelMinBackdropDownloadWidth": "\u0410\u0440\u0442\u049b\u044b \u0441\u0443\u0440\u0435\u0442\u0442\u0456\u04a3 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0430\u0442\u044b\u043d \u0435\u04a3 \u0430\u0437 \u0435\u043d\u0456:",
@@ -561,6 +578,9 @@
     "NotificationOptionVideoPlayback": "\u0411\u0435\u0439\u043d\u0435 \u043e\u0439\u043d\u0430\u0442\u0443",
     "NotificationOptionAudioPlayback": "\u0414\u044b\u0431\u044b\u0441 \u043e\u0439\u043d\u0430\u0442\u0443",
     "NotificationOptionGamePlayback": "\u041e\u0439\u044b\u043d \u043e\u0439\u043d\u0430\u0442\u0443",
+    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
     "NotificationOptionTaskFailed": "\u0416\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d \u0442\u0430\u043f\u0441\u044b\u0440\u043c\u0430 \u0441\u04d9\u0442\u0441\u0456\u0437\u0434\u0456\u0433\u0456",
     "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",
@@ -588,6 +608,7 @@
     "ButtonArrowRight": "\u041e\u04a3 \u0436\u0430\u049b\u049b\u0430",
     "ButtonBack": "\u0410\u0440\u0442\u049b\u0430",
     "ButtonInfo": "\u0410\u049b\u043f\u0430\u0440\u0430\u0442",
+    "ButtonOsd": "On screen display",
     "ButtonPageUp": "\u0416\u043e\u0493\u0430\u0440\u0493\u044b \u0431\u0435\u0442\u043a\u0435",
     "ButtonPageDown": "\u0422\u04e9\u043c\u0435\u043d\u0433\u0456 \u0431\u0435\u0442\u043a\u0435",
     "PageAbbreviation": "PG",
@@ -616,7 +637,6 @@
     "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",
-    "OptionNoSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440 \u0436\u043e\u049b",
     "OptionSpecialFeatures": "\u0410\u0440\u043d\u0430\u0439\u044b \u049b\u043e\u0441\u044b\u043c\u0448\u0430\u043b\u0430\u0440",
     "HeaderCollections": "Collections",
     "HeaderMyLibrary": "My Library",
@@ -704,12 +724,26 @@
     "TabSubtitles": "Subtitles",
     "LabelOpenSubtitlesUsername": "Open Subtitles username:",
     "LabelOpenSubtitlesPassword": "Open Subtitles password:",
-    "LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language.",
+    "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:"
+    "LabelMessageText": "Message text:",
+    "MessageNoAvailablePlugins": "No available plugins.",
+    "LabelDisplayPluginsFor": "Display plugins for:",
+    "PluginTabMediaBrowserClassic": "MB Classic",
+    "PluginTabMediaBrowserTheater": "MB Theater",
+    "TabOtherPlugins": "Others",
+    "LabelEpisodeName": "Episode name",
+    "LabelSeriesName": "Series name",
+    "ValueSeriesNamePeriod": "Series.name",
+    "ValueSeriesNameUnderscore": "Series_name",
+    "ValueEpisodeNamePeriod": "Episode.name",
+    "ValueEpisodeNameUnderscore": "Episode_name",
+    "HeaderTypeText": "Enter Text",
+    "LabelTypeText": "Text"
 }

部分文件因为文件数量过多而无法显示