diff options
125 files changed, 3374 insertions, 976 deletions
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 96fe01ab3..abeaba910 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -39,6 +39,9 @@ namespace MediaBrowser.Api.LiveTv /// <value>The limit.</value> [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } + + [ApiMember(Name = "IsFavorite", Description = "Filter by channels that are favorites, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsFavorite { get; set; } } [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")] @@ -290,7 +293,8 @@ namespace MediaBrowser.Api.LiveTv ChannelType = request.Type, UserId = request.UserId, StartIndex = request.StartIndex, - Limit = request.Limit + Limit = request.Limit, + IsFavorite = request.IsFavorite }, CancellationToken.None).Result; diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 60270a389..be86090ac 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -32,6 +32,14 @@ namespace MediaBrowser.Api } /// <summary> + /// Class ParentalRatings + /// </summary> + [Route("/Localization/Options", "GET", Summary = "Gets localization options")] + public class GetLocalizationOptions : IReturn<List<LocalizatonOption>> + { + } + + /// <summary> /// Class CulturesService /// </summary> public class LocalizationService : BaseApiService @@ -62,6 +70,13 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + public object Get(GetLocalizationOptions request) + { + var result = _localization.GetLocalizationOptions().ToList(); + + return ToOptimizedSerializedResultUsingCache(result); + } + /// <summary> /// Gets the specified request. /// </summary> diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 18559a68d..c03eddf99 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -103,6 +103,7 @@ <Compile Include="Playback\Hls\DynamicHlsService.cs" /> <Compile Include="Playback\Hls\HlsSegmentService.cs" /> <Compile Include="Playback\Hls\VideoHlsService.cs" /> + <Compile Include="Playback\ProgressiveStreamService.cs" /> <Compile Include="Playback\Progressive\AudioService.cs" /> <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" /> <Compile Include="Playback\BaseStreamingService.cs" /> diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 9b9df3a92..c39811bb6 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -1,9 +1,9 @@ using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; using ServiceStack; -using System; using System.Collections.Generic; using System.Linq; @@ -36,103 +36,74 @@ namespace MediaBrowser.Api.Music public class InstantMixService : BaseApiService { private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; + private readonly IMusicManager _musicManager; - public InstantMixService(IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService) + public InstantMixService(IUserManager userManager, IDtoService dtoService, IMusicManager musicManager) { _userManager = userManager; - _libraryManager = libraryManager; _dtoService = dtoService; + _musicManager = musicManager; } public object Get(GetInstantMixFromSong request) { - var item = _dtoService.GetItemByDtoId(request.Id); + var item = (Audio)_dtoService.GetItemByDtoId(request.Id); - var result = GetInstantMixResult(request, item.Genres); + var user = _userManager.GetUserById(request.UserId.Value); - return ToOptimizedSerializedResultUsingCache(result); + var items = _musicManager.GetInstantMixFromSong(item, user); + + return GetResult(items, user, request); } public object Get(GetInstantMixFromAlbum request) { var album = (MusicAlbum)_dtoService.GetItemByDtoId(request.Id); - var genres = album - .RecursiveChildren - .OfType<Audio>() - .SelectMany(i => i.Genres) - .Concat(album.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase); + var user = _userManager.GetUserById(request.UserId.Value); - var result = GetInstantMixResult(request, genres); + var items = _musicManager.GetInstantMixFromAlbum(album, user); - return ToOptimizedSerializedResultUsingCache(result); + return GetResult(items, user, request); } public object Get(GetInstantMixFromMusicGenre request) { - var genre = GetMusicGenre(request.Name, _libraryManager); + var user = _userManager.GetUserById(request.UserId.Value); - var result = GetInstantMixResult(request, new[] { genre.Name }); + var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user); - return ToOptimizedSerializedResultUsingCache(result); + return GetResult(items, user, request); } public object Get(GetInstantMixFromArtist request) { - var artist = GetArtist(request.Name, _libraryManager); - - var genres = _libraryManager.RootFolder - .RecursiveChildren - .OfType<Audio>() - .Where(i => i.HasArtist(artist.Name)) - .SelectMany(i => i.Genres) - .Concat(artist.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase); + var user = _userManager.GetUserById(request.UserId.Value); - var result = GetInstantMixResult(request, genres); + var items = _musicManager.GetInstantMixFromArtist(request.Name, user); - return ToOptimizedSerializedResultUsingCache(result); + return GetResult(items, user, request); } - private ItemsResult GetInstantMixResult(BaseGetSimilarItems request, IEnumerable<string> genres) + private object GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request) { - var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - var fields = request.GetItemFields().ToList(); - var inputItems = user == null - ? _libraryManager.RootFolder.RecursiveChildren - : user.RootFolder.GetRecursiveChildren(user); - - var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - - var limit = request.Limit.HasValue ? request.Limit.Value * 2 : 100; - - var items = inputItems - .OfType<Audio>() - .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey))) - .OrderByDescending(i => i.Item2) - .ThenBy(i => Guid.NewGuid()) - .Select(i => i.Item1) - .Take(limit) - .OrderBy(i => Guid.NewGuid()) - .ToList(); + var list = items.ToList(); var result = new ItemsResult { - TotalRecordCount = items.Count + TotalRecordCount = list.Count }; - var dtos = items.Take(request.Limit ?? items.Count) + var dtos = list.Take(request.Limit ?? list.Count) .Select(i => _dtoService.GetBaseItemDto(i, fields, user)); result.Items = dtos.ToArray(); - return result; + return ToOptimizedResult(result); } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 519ff7947..6c406a11c 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -734,7 +734,9 @@ namespace MediaBrowser.Api.Playback { if (audioStream != null) { - if (audioStream.Channels > 2 && string.Equals(request.AudioCodec, "wma", StringComparison.OrdinalIgnoreCase)) + var codec = request.AudioCodec ?? string.Empty; + + if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) { // wmav2 currently only supports two channel output return 2; @@ -835,11 +837,6 @@ namespace MediaBrowser.Api.Playback /// <returns>System.String.</returns> protected string GetInputArgument(StreamState state) { - if (state.SendInputOverStandardInput) - { - return "-"; - } - var type = InputType.File; var inputPath = new[] { state.MediaPath }; @@ -898,9 +895,7 @@ namespace MediaBrowser.Api.Playback Arguments = commandLineArgs, WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - - RedirectStandardInput = state.SendInputOverStandardInput + ErrorDialog = false }, EnableRaisingEvents = true @@ -933,11 +928,6 @@ namespace MediaBrowser.Api.Playback throw; } - if (state.SendInputOverStandardInput) - { - StreamToStandardInput(process, state); - } - // MUST read both stdout and stderr asynchronously or a deadlock may occurr process.BeginOutputReadLine(); @@ -965,32 +955,6 @@ namespace MediaBrowser.Api.Playback } } - private async void StreamToStandardInput(Process process, StreamState state) - { - try - { - await StreamToStandardInputInternal(process, state).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - Logger.Debug("Stream to standard input closed normally."); - } - catch (Exception ex) - { - Logger.ErrorException("Error writing to standard input", ex); - } - } - - private async Task StreamToStandardInputInternal(Process process, StreamState state) - { - state.StandardInputCancellationTokenSource = new CancellationTokenSource(); - - using (var fileStream = FileSystem.GetFileStream(state.MediaPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) - { - await new EndlessStreamCopy().CopyStream(fileStream, process.StandardInput.BaseStream, state.StandardInputCancellationTokenSource.Token).ConfigureAwait(false); - } - } - protected int? GetVideoBitrateParamValue(StreamState state) { var bitrate = state.VideoRequest.VideoBitRate; @@ -1280,22 +1244,12 @@ namespace MediaBrowser.Api.Playback } else if (i == 14) { - if (videoRequest != null) - { - videoRequest.Framerate = int.Parse(val, UsCulture); - } + request.StartTimeTicks = long.Parse(val, UsCulture); } else if (i == 15) { if (videoRequest != null) { - request.StartTimeTicks = long.Parse(val, UsCulture); - } - } - else if (i == 16) - { - if (videoRequest != null) - { videoRequest.Level = val; } } @@ -1315,11 +1269,6 @@ namespace MediaBrowser.Api.Playback ParseParams(request); } - if (request.ThrowDebugError) - { - throw new InvalidOperationException("You asked for a debug error, you got one."); - } - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager); var url = Request.PathInfo; @@ -1369,8 +1318,6 @@ namespace MediaBrowser.Api.Playback { state.MediaPath = path; state.IsRemote = false; - - state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress; } else if (!string.IsNullOrEmpty(mediaUrl)) { @@ -1378,7 +1325,8 @@ namespace MediaBrowser.Api.Playback state.IsRemote = true; } - //state.RunTimeTicks = recording.RunTimeTicks; + state.RunTimeTicks = recording.RunTimeTicks; + if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote) { await Task.Delay(1000, cancellationToken).ConfigureAwait(false); @@ -1544,21 +1492,9 @@ namespace MediaBrowser.Api.Playback state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - foreach (var setting in transcodingProfile.Settings) + if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.Profile)) { - switch (setting.Name) - { - case TranscodingSettingType.VideoProfile: - { - if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.Profile)) - { - state.VideoRequest.Profile = setting.Value; - } - break; - } - default: - throw new ArgumentException("Unrecognized TranscodingSettingType"); - } + state.VideoRequest.Profile = transcodingProfile.VideoProfile; } } } @@ -1575,12 +1511,6 @@ namespace MediaBrowser.Api.Playback { var timeSeek = GetHeader("TimeSeekRange.dlna.org"); - if (!string.IsNullOrEmpty(timeSeek)) - { - ResultFactory.ThrowError(406, "Time seek not supported during encoding.", responseHeaders); - return; - } - var transferMode = GetHeader("transferMode.dlna.org"); responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; @@ -1589,7 +1519,13 @@ namespace MediaBrowser.Api.Playback var extension = GetOutputFileExtension(state); // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none - var orgOp = isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? ";DLNA.ORG_OP=01" : ";DLNA.ORG_OP=00"; + var orgOp = ";DLNA.ORG_OP="; + + // Time-based seeking currently only possible when transcoding + orgOp += isStaticallyStreamed ? "0" : "1"; + + // Byte-based seeking only possible when not transcoding + orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0"; // 0 = native, 1 = transcoded var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 96b36ac7f..1a190b75e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Playback.Hls ? 0 : ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs; - var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds); + var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture)); var threads = GetNumberOfThreads(state, false); diff --git a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs new file mode 100644 index 000000000..531f79a22 --- /dev/null +++ b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Api.Playback.Progressive; + +namespace MediaBrowser.Api.Playback +{ + //public class GetProgressiveAudioStream : StreamRequest + //{ + + //} + + //public class ProgressiveStreamService : BaseApiService + //{ + // public object Get(GetProgressiveAudioStream request) + // { + // return ProcessRequest(request, false); + // } + + // /// <summary> + // /// Gets the specified request. + // /// </summary> + // /// <param name="request">The request.</param> + // /// <returns>System.Object.</returns> + // public object Head(GetProgressiveAudioStream request) + // { + // return ProcessRequest(request, true); + // } + + // protected object ProcessRequest(StreamRequest request, bool isHeadRequest) + // { + // var state = GetState(request, CancellationToken.None).Result; + + // var responseHeaders = new Dictionary<string, string>(); + + // if (request.Static && state.IsRemote) + // { + // AddDlnaHeaders(state, responseHeaders, true); + + // return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result; + // } + + // var outputPath = GetOutputFilePath(state); + // var outputPathExists = File.Exists(outputPath); + + // var isStatic = request.Static || + // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); + + // AddDlnaHeaders(state, responseHeaders, isStatic); + + // if (request.Static) + // { + // var contentType = state.GetMimeType(state.MediaPath); + + // return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); + // } + + // if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) + // { + // var contentType = state.GetMimeType(outputPath); + + // return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); + // } + + // return GetStreamResult(state, responseHeaders, isHeadRequest).Result; + // } + + //} +} diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 0eb2984fb..add517b5d 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -67,11 +67,6 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceProfileId { get; set; } - - /// <summary> - /// For testing purposes - /// </summary> - public bool ThrowDebugError { get; set; } public string Params { get; set; } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 504d7d921..48285a4b1 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -51,8 +51,6 @@ namespace MediaBrowser.Api.Playback public bool HasMediaStreams { get; set; } - public bool SendInputOverStandardInput { get; set; } - public CancellationTokenSource StandardInputCancellationTokenSource { get; set; } public string LiveTvStreamId { get; set; } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index a509c876c..1f3bcf75b 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -146,7 +146,36 @@ namespace MediaBrowser.Api /// </summary> /// <value>The play command.</value> [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public SystemCommand Command { get; set; } + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + public class SendGeneralCommand : IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } + + /// <summary> + /// Gets or sets the command. + /// </summary> + /// <value>The play command.</value> + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + public class SendFullGeneralCommand : GeneralCommand, IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } } [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] @@ -301,9 +330,22 @@ namespace MediaBrowser.Api /// <param name="request">The request.</param> public void Post(SendSystemCommand request) { - var task = _sessionManager.SendSystemCommand(GetSession().Id, request.Id, request.Command, CancellationToken.None); + GeneralCommandType commandType; - Task.WaitAll(task); + if (Enum.TryParse(request.Command, true, out commandType)) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = commandType.ToString(), + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } } /// <summary> @@ -343,6 +385,32 @@ namespace MediaBrowser.Api Task.WaitAll(task); } + public void Post(SendGeneralCommand request) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = request.Command, + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendFullGeneralCommand request) + { + var currentSession = GetSession(); + + request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); + + Task.WaitAll(task); + } + public void Post(AddUserToSession request) { _sessionManager.AddAdditionalUser(request.Id, request.UserId); diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index c1fc713e4..bb9629c28 100644 --- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -74,13 +74,13 @@ namespace MediaBrowser.Controller.Dlna public ContainerProfile[] ContainerProfiles { get; set; } public CodecProfile[] CodecProfiles { get; set; } - public MediaProfile[] MediaProfiles { get; set; } + public ResponseProfile[] ResponseProfiles { get; set; } public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; TranscodingProfiles = new TranscodingProfile[] { }; - MediaProfiles = new MediaProfile[] { }; + ResponseProfiles = new ResponseProfile[] { }; CodecProfiles = new CodecProfile[] { }; ContainerProfiles = new ContainerProfile[] { }; @@ -147,11 +147,11 @@ namespace MediaBrowser.Controller.Dlna }); } - public MediaProfile GetAudioMediaProfile(string container, string audioCodec, MediaStream audioStream) + public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, MediaStream audioStream) { container = (container ?? string.Empty).TrimStart('.'); - return MediaProfiles.FirstOrDefault(i => + return ResponseProfiles.FirstOrDefault(i => { if (i.Type != DlnaProfileType.Audio) { @@ -174,11 +174,11 @@ namespace MediaBrowser.Controller.Dlna }); } - public MediaProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec, MediaStream audioStream, MediaStream videoStream) + public ResponseProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec, MediaStream audioStream, MediaStream videoStream) { container = (container ?? string.Empty).TrimStart('.'); - return MediaProfiles.FirstOrDefault(i => + return ResponseProfiles.FirstOrDefault(i => { if (i.Type != DlnaProfileType.Video) { @@ -207,11 +207,11 @@ namespace MediaBrowser.Controller.Dlna }); } - public MediaProfile GetPhotoMediaProfile(string container) + public ResponseProfile GetPhotoMediaProfile(string container) { container = (container ?? string.Empty).TrimStart('.'); - return MediaProfiles.FirstOrDefault(i => + return ResponseProfiles.FirstOrDefault(i => { if (i.Type != DlnaProfileType.Photo) { diff --git a/MediaBrowser.Controller/Dlna/MediaProfile.cs b/MediaBrowser.Controller/Dlna/ResponseProfile.cs index bf3057294..163a95d5a 100644 --- a/MediaBrowser.Controller/Dlna/MediaProfile.cs +++ b/MediaBrowser.Controller/Dlna/ResponseProfile.cs @@ -4,7 +4,7 @@ using System.Xml.Serialization; namespace MediaBrowser.Controller.Dlna { - public class MediaProfile + public class ResponseProfile { [XmlAttribute("container")] public string Container { get; set; } @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Dlna public ProfileCondition[] Conditions { get; set; } - public MediaProfile() + public ResponseProfile() { Conditions = new ProfileCondition[] {}; } diff --git a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs index 707f0c573..704ba54d2 100644 --- a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs @@ -30,13 +30,8 @@ namespace MediaBrowser.Controller.Dlna [XmlAttribute("transcodeSeekInfo")] public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - public TranscodingSetting[] Settings { get; set; } - - public TranscodingProfile() - { - Settings = new TranscodingSetting[] { }; - } - + [XmlAttribute("videoProfile")] + public string VideoProfile { get; set; } public List<string> GetAudioCodecs() { @@ -44,20 +39,6 @@ namespace MediaBrowser.Controller.Dlna } } - public class TranscodingSetting - { - [XmlAttribute("name")] - public TranscodingSettingType Name { get; set; } - - [XmlAttribute("value")] - public string Value { get; set; } - } - - public enum TranscodingSettingType - { - VideoProfile = 0 - } - public enum TranscodeSeekInfo { Auto = 0, diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index a02851a9f..2704959e4 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -86,6 +86,13 @@ namespace MediaBrowser.Controller.Dto ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item); /// <summary> + /// Gets the media sources. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>List{MediaSourceInfo}.</returns> + List<MediaSourceInfo> GetMediaSources(BaseItem item); + + /// <summary> /// Gets the item by name dto. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs new file mode 100644 index 000000000..192ce2e83 --- /dev/null +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Library +{ + public interface IMusicManager + { + /// <summary> + /// Gets the instant mix from song. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{Audio}.</returns> + IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user); + /// <summary> + /// Gets the instant mix from artist. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{Audio}.</returns> + IEnumerable<Audio> GetInstantMixFromArtist(string name, User user); + /// <summary> + /// Gets the instant mix from album. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{Audio}.</returns> + IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user); + /// <summary> + /// Gets the instant mix from genre. + /// </summary> + /// <param name="genres">The genres.</param> + /// <param name="user">The user.</param> + /// <returns>IEnumerable{Audio}.</returns> + IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user); + } +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index edf86cae8..f8cdc6eee 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -1,8 +1,8 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Library; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Library; namespace MediaBrowser.Controller.LiveTv { @@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.LiveTv RecordingInfo RecordingInfo { get; set; } + long? RunTimeTicks { get; set; } + string GetClientTypeName(); string GetUserDataKey(); diff --git a/MediaBrowser.Controller/Localization/ILocalizationManager.cs b/MediaBrowser.Controller/Localization/ILocalizationManager.cs index dde2f7878..f41940ed4 100644 --- a/MediaBrowser.Controller/Localization/ILocalizationManager.cs +++ b/MediaBrowser.Controller/Localization/ILocalizationManager.cs @@ -1,7 +1,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using System; using System.Collections.Generic; -using System.Threading.Tasks; namespace MediaBrowser.Controller.Localization { @@ -31,5 +31,42 @@ namespace MediaBrowser.Controller.Localization /// <param name="rating">The rating.</param> /// <returns>System.Int32.</returns> int? GetRatingLevel(string rating); + + /// <summary> + /// Gets the localized string. + /// </summary> + /// <param name="phrase">The phrase.</param> + /// <param name="culture">The culture.</param> + /// <returns>System.String.</returns> + string GetLocalizedString(string phrase, string culture); + + /// <summary> + /// Gets the localized string. + /// </summary> + /// <param name="phrase">The phrase.</param> + /// <returns>System.String.</returns> + string GetLocalizedString(string phrase); + + /// <summary> + /// Localizes the document. + /// </summary> + /// <param name="document">The document.</param> + /// <param name="culture">The culture.</param> + /// <param name="tokenBuilder">The token builder.</param> + /// <returns>System.String.</returns> + string LocalizeDocument(string document, string culture, Func<string, string> tokenBuilder); + + /// <summary> + /// Gets the localization options. + /// </summary> + /// <returns>IEnumerable{LocalizatonOption}.</returns> + IEnumerable<LocalizatonOption> GetLocalizationOptions(); + + /// <summary> + /// Gets the java script localization dictionary. + /// </summary> + /// <param name="culture">The culture.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 9915ac044..a233c1f12 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -84,7 +84,7 @@ <Compile Include="Dlna\DirectPlayProfile.cs" /> <Compile Include="Dlna\IDlnaManager.cs" /> <Compile Include="Dlna\DeviceProfile.cs" /> - <Compile Include="Dlna\MediaProfile.cs" /> + <Compile Include="Dlna\ResponseProfile.cs" /> <Compile Include="Dlna\TranscodingProfile.cs" /> <Compile Include="Drawing\IImageProcessor.cs" /> <Compile Include="Drawing\ImageFormat.cs" /> @@ -134,6 +134,7 @@ <Compile Include="Library\DeleteOptions.cs" /> <Compile Include="Library\ILibraryPostScanTask.cs" /> <Compile Include="Library\IMetadataSaver.cs" /> + <Compile Include="Library\IMusicManager.cs" /> <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Library\IUserDataManager.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" /> @@ -155,11 +156,14 @@ <Compile Include="LiveTv\SeriesTimerInfo.cs" /> <Compile Include="LiveTv\TimerInfo.cs" /> <Compile Include="Localization\ILocalizationManager.cs" /> + <Compile Include="MediaEncoding\EncodingOptions.cs" /> <Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" /> + <Compile Include="MediaEncoding\EncodingResult.cs" /> <Compile Include="MediaEncoding\IEncodingManager.cs" /> <Compile Include="MediaEncoding\ImageEncodingOptions.cs" /> <Compile Include="MediaEncoding\IMediaEncoder.cs" /> <Compile Include="MediaEncoding\InternalMediaInfoResult.cs" /> + <Compile Include="MediaEncoding\VideoEncodingOptions.cs" /> <Compile Include="Net\IHasResultFactory.cs" /> <Compile Include="Net\IHttpResultFactory.cs" /> <Compile Include="Net\IHttpServer.cs" /> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs new file mode 100644 index 000000000..74235becd --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs @@ -0,0 +1,79 @@ +using MediaBrowser.Controller.Dlna; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class EncodingOptions + { + /// <summary> + /// Gets or sets the item identifier. + /// </summary> + /// <value>The item identifier.</value> + public string ItemId { get; set; } + + /// <summary> + /// Gets or sets the media source identifier. + /// </summary> + /// <value>The media source identifier.</value> + public string MediaSourceId { get; set; } + + /// <summary> + /// Gets or sets the device profile. + /// </summary> + /// <value>The device profile.</value> + public DeviceProfile DeviceProfile { get; set; } + + /// <summary> + /// Gets or sets the output path. + /// </summary> + /// <value>The output path.</value> + public string OutputPath { get; set; } + + /// <summary> + /// Gets or sets the container. + /// </summary> + /// <value>The container.</value> + public string Container { get; set; } + + /// <summary> + /// Gets or sets the audio codec. + /// </summary> + /// <value>The audio codec.</value> + public string AudioCodec { get; set; } + + /// <summary> + /// Gets or sets the start time ticks. + /// </summary> + /// <value>The start time ticks.</value> + public long? StartTimeTicks { get; set; } + + /// <summary> + /// Gets or sets the maximum channels. + /// </summary> + /// <value>The maximum channels.</value> + public int? MaxAudioChannels { get; set; } + + /// <summary> + /// Gets or sets the channels. + /// </summary> + /// <value>The channels.</value> + public int? AudioChannels { get; set; } + + /// <summary> + /// Gets or sets the sample rate. + /// </summary> + /// <value>The sample rate.</value> + public int? AudioSampleRate { get; set; } + + /// <summary> + /// Gets or sets the bit rate. + /// </summary> + /// <value>The bit rate.</value> + public int? AudioBitRate { get; set; } + + /// <summary> + /// Gets or sets the maximum audio bit rate. + /// </summary> + /// <value>The maximum audio bit rate.</value> + public int? MaxAudioBitRate { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs b/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs new file mode 100644 index 000000000..75ee90e42 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class EncodingResult + { + public string OutputPath { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs new file mode 100644 index 000000000..773f0ea46 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs @@ -0,0 +1,26 @@ + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class VideoEncodingOptions : EncodingOptions + { + public string VideoCodec { get; set; } + + public string VideoProfile { get; set; } + + public double? VideoLevel { get; set; } + + public int? VideoStreamIndex { get; set; } + + public int? AudioStreamIndex { get; set; } + + public int? SubtitleStreamIndex { get; set; } + + public int? MaxWidth { get; set; } + + public int? MaxHeight { get; set; } + + public int? Height { get; set; } + + public int? Width { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 02cc875bd..cf57f6621 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -20,14 +20,6 @@ namespace MediaBrowser.Controller.Session bool IsSessionActive { get; } /// <summary> - /// Sends the system command. - /// </summary> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken); - - /// <summary> /// Sends the message command. /// </summary> /// <param name="command">The command.</param> @@ -65,7 +57,7 @@ namespace MediaBrowser.Controller.Session /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken); + Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken); /// <summary> /// Sends the library update info. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 459e43d08..434c51336 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -84,15 +84,15 @@ namespace MediaBrowser.Controller.Session Task ReportSessionEnded(Guid sessionId); /// <summary> - /// Sends the system command. + /// Sends the general command. /// </summary> /// <param name="controllingSessionId">The controlling session identifier.</param> - /// <param name="sessionId">The session id.</param> + /// <param name="sessionId">The session identifier.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken); - + Task SendGeneralCommand(Guid controllingSessionId, Guid sessionId, GeneralCommand command, CancellationToken cancellationToken); + /// <summary> /// Sends the message command. /// </summary> diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index ec9ecb9ef..958188f37 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -20,13 +20,15 @@ namespace MediaBrowser.Dlna private readonly IXmlSerializer _xmlSerializer; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; - public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger) + public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; _appPaths = appPaths; _logger = logger; + _jsonSerializer = jsonSerializer; //DumpProfiles(); } @@ -381,10 +383,66 @@ namespace MediaBrowser.Dlna public void CreateProfile(DeviceProfile profile) { + profile = ReserializeProfile(profile); + + if (string.IsNullOrWhiteSpace(profile.Name)) + { + throw new ArgumentException("Profile is missing Name"); + } + + var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml"; + var path = Path.Combine(UserProfilesPath, newFilename); + + _xmlSerializer.SerializeToFile(profile, path); } public void UpdateProfile(DeviceProfile profile) { + profile = ReserializeProfile(profile); + + if (string.IsNullOrWhiteSpace(profile.Id)) + { + throw new ArgumentException("Profile is missing Id"); + } + if (string.IsNullOrWhiteSpace(profile.Name)) + { + throw new ArgumentException("Profile is missing Name"); + } + + var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase)); + + if (current.Info.Type == DeviceProfileType.System) + { + throw new ArgumentException("System profiles are readonly"); + } + + var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml"; + var path = Path.Combine(UserProfilesPath, newFilename); + + if (!string.Equals(path, current.Path, StringComparison.Ordinal)) + { + File.Delete(current.Path); + } + + _xmlSerializer.SerializeToFile(profile, path); + } + + /// <summary> + /// Recreates the object using serialization, to ensure it's not a subclass. + /// If it's a subclass it may not serlialize properly to xml (different root element tag name) + /// </summary> + /// <param name="profile"></param> + /// <returns></returns> + private DeviceProfile ReserializeProfile(DeviceProfile profile) + { + if (profile.GetType() == typeof(DeviceProfile)) + { + return profile; + } + + var json = _jsonSerializer.SerializeToString(profile); + + return _jsonSerializer.DeserializeFromString<DeviceProfile>(json); } class InternalProfileInfo diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 0c9f292ad..96df8c862 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -308,25 +308,6 @@ namespace MediaBrowser.Dlna.PlayTo return Task.FromResult(true); } - public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken) - { - switch (command) - { - case SystemCommand.VolumeDown: - return _device.VolumeDown(); - case SystemCommand.VolumeUp: - return _device.VolumeUp(); - case SystemCommand.Mute: - return _device.VolumeDown(true); - case SystemCommand.Unmute: - return _device.VolumeUp(true); - case SystemCommand.ToggleMute: - return _device.ToggleMute(); - default: - return Task.FromResult(true); - } - } - public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) { return Task.FromResult(true); @@ -620,9 +601,30 @@ namespace MediaBrowser.Dlna.PlayTo } } - public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken) + public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { - throw new NotImplementedException(); + GeneralCommandType commandType; + + if (!Enum.TryParse(command.Name, true, out commandType)) + { + switch (commandType) + { + case GeneralCommandType.VolumeDown: + return _device.VolumeDown(); + case GeneralCommandType.VolumeUp: + return _device.VolumeUp(); + case GeneralCommandType.Mute: + return _device.VolumeDown(true); + case GeneralCommandType.Unmute: + return _device.VolumeUp(true); + case GeneralCommandType.ToggleMute: + return _device.ToggleMute(); + default: + return Task.FromResult(true); + } + } + + return Task.FromResult(true); } } } diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index 50605c61f..9f990bcb7 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Dlna; -using System.Collections.Generic; namespace MediaBrowser.Dlna.PlayTo { @@ -27,8 +26,6 @@ namespace MediaBrowser.Dlna.PlayTo public string AudioCodec { get; set; } - public List<TranscodingSetting> TranscodingSettings { get; set; } - public int? AudioStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; } @@ -47,10 +44,5 @@ namespace MediaBrowser.Dlna.PlayTo public int? MaxFramerate { get; set; } public string DeviceProfileId { get; set; } - - public PlaylistItem() - { - TranscodingSettings = new List<TranscodingSetting>(); - } } }
\ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs index c14a851ca..6a42e6a75 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -48,7 +48,6 @@ namespace MediaBrowser.Dlna.PlayTo if (transcodingProfile != null) { playlistItem.Transcode = true; - playlistItem.TranscodingSettings = transcodingProfile.Settings.ToList(); playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); playlistItem.AudioCodec = transcodingProfile.AudioCodec; @@ -88,7 +87,6 @@ namespace MediaBrowser.Dlna.PlayTo if (transcodingProfile != null) { playlistItem.Transcode = true; - playlistItem.TranscodingSettings = transcodingProfile.Settings.ToList(); playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); } @@ -137,7 +135,6 @@ namespace MediaBrowser.Dlna.PlayTo if (transcodingProfile != null) { playlistItem.Transcode = true; - playlistItem.TranscodingSettings = transcodingProfile.Settings.ToList(); playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault(); playlistItem.VideoCodec = transcodingProfile.VideoCodec; diff --git a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs index 6b5513e28..e6b5668fa 100644 --- a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs +++ b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs @@ -35,11 +35,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video, AudioCodec = "aac", VideoCodec = "h264", - - Settings = new [] - { - new TranscodingSetting {Name = TranscodingSettingType.VideoProfile, Value = "baseline"} - } + VideoProfile= "baseline" } }; diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs index 122bde875..b008947d3 100644 --- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -302,16 +302,16 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "avi", MimeType = "video/x-msvideo", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mkv", MimeType = "video/x-mkv", diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs index c5025edbb..972fc48ed 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayerProfile.cs @@ -206,9 +206,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec = "h264,mpeg4,vc1", @@ -218,42 +218,42 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "avi", MimeType = "video/mpeg", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mkv", MimeType = "video/vnd.dlna.mpeg-tts", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", MimeType = "video/vnd.dlna.mpeg-tts", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mp4", MimeType = "video/mpeg", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mpeg", MimeType = "video/mpeg", Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mp3", MimeType = "audio/mpeg", diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs index 8f29ad76e..870b97fe7 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs @@ -89,9 +89,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -101,7 +101,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -111,7 +111,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -121,7 +121,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="mpeg2video", @@ -130,7 +130,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mpeg", VideoCodec="mpeg1video,mpeg2video", diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs index eaf6979a6..2bba58696 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs @@ -131,9 +131,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -143,7 +143,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -153,7 +153,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -163,7 +163,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="mpeg2video", @@ -172,7 +172,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mpeg", VideoCodec="mpeg1video,mpeg2video", diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs index 41d057ef8..f8a6dcfbd 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs @@ -119,9 +119,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -131,7 +131,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -141,7 +141,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -151,7 +151,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="mpeg2video", @@ -160,7 +160,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mpeg", VideoCodec="mpeg1video,mpeg2video", diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs index 386a36ae0..56eaf47f4 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs @@ -175,9 +175,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -187,7 +187,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -197,7 +197,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="h264", @@ -207,7 +207,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "ts", VideoCodec="mpeg2video", @@ -216,7 +216,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "mpeg", VideoCodec="mpeg1video,mpeg2video", diff --git a/MediaBrowser.Dlna/Profiles/SonyPs3Profile.cs b/MediaBrowser.Dlna/Profiles/SonyPs3Profile.cs index 351a13f00..06d721f52 100644 --- a/MediaBrowser.Dlna/Profiles/SonyPs3Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyPs3Profile.cs @@ -207,9 +207,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "mp4,mov", AudioCodec="aac", @@ -217,7 +217,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "avi", MimeType = "video/divx", @@ -225,7 +225,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video }, - new MediaProfile + new ResponseProfile { Container = "wav", MimeType = "audio/wav", diff --git a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs index f0b95d4e8..c3b88f7bf 100644 --- a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs +++ b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs @@ -43,11 +43,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video, VideoCodec = "h264", AudioCodec = "aac", - - Settings = new [] - { - new TranscodingSetting {Name = TranscodingSettingType.VideoProfile, Value = "baseline"} - } + VideoProfile= "baseline" }, new TranscodingProfile { @@ -157,9 +153,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "ts", OrgPn = "MPEG_TS_SD_NA", diff --git a/MediaBrowser.Dlna/Profiles/Xbox360Profile.cs b/MediaBrowser.Dlna/Profiles/Xbox360Profile.cs index 38d08adef..3fae85f59 100644 --- a/MediaBrowser.Dlna/Profiles/Xbox360Profile.cs +++ b/MediaBrowser.Dlna/Profiles/Xbox360Profile.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Dlna.Profiles { ModelName = "Xbox 360", - Headers = new [] + Headers = new[] { new HttpHeaderInfo {Name = "User-Agent", Value = "Xbox", Match = HeaderMatchType.Substring}, new HttpHeaderInfo {Name = "User-Agent", Value = "Xenon", Match = HeaderMatchType.Substring} @@ -48,11 +48,7 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Video, TranscodeSeekInfo = TranscodeSeekInfo.Bytes, EstimateContentLength = true, - - Settings = new [] - { - new TranscodingSetting {Name = TranscodingSettingType.VideoProfile, Value = "baseline"} - } + VideoProfile= "baseline" }, new TranscodingProfile { @@ -110,9 +106,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "avi", MimeType = "video/avi", diff --git a/MediaBrowser.Dlna/Profiles/XboxOneProfile.cs b/MediaBrowser.Dlna/Profiles/XboxOneProfile.cs index 058c69e1f..59372655c 100644 --- a/MediaBrowser.Dlna/Profiles/XboxOneProfile.cs +++ b/MediaBrowser.Dlna/Profiles/XboxOneProfile.cs @@ -42,9 +42,9 @@ namespace MediaBrowser.Dlna.Profiles } }; - MediaProfiles = new[] + ResponseProfiles = new[] { - new MediaProfile + new ResponseProfile { Container = "avi", MimeType = "video/x-msvideo", diff --git a/MediaBrowser.Dlna/Profiles/Xml/Default.xml b/MediaBrowser.Dlna/Profiles/Xml/Default.xml index 895cb99d3..9d72d68d2 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Default.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Default.xml @@ -20,16 +20,10 @@ <DirectPlayProfile container="avi,mp4" type="Video" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" videoProfile="baseline" /> </TranscodingProfiles> <ContainerProfiles /> <CodecProfiles /> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml index 58c5cefbc..31ab8b85c 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml @@ -24,16 +24,10 @@ <DirectPlayProfile container="mp3,flac,m4a,wma" type="Audio" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" videoProfile="baseline" /> </TranscodingProfiles> <ContainerProfiles /> <CodecProfiles /> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml index 53781ad32..0e9ce618e 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml @@ -29,15 +29,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -69,5 +63,5 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml index fc833b918..39822e0a3 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml @@ -24,16 +24,10 @@ <DirectPlayProfile container="avi,mp4,mkv,ts" type="Video" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" videoProfile="baseline" /> </TranscodingProfiles> <ContainerProfiles /> <CodecProfiles /> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml index 49fd05b1e..ab815a645 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -35,15 +35,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -62,5 +56,5 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml index 75c50aae3..bd17802a2 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -33,15 +33,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -91,12 +85,12 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="avi" type="Video" mimeType="video/x-msvideo"> + <ResponseProfiles> + <ResponseProfile container="avi" type="Video" mimeType="video/x-msvideo"> <Conditions /> - </MediaProfile> - <MediaProfile container="mkv" type="Video" mimeType="video/x-mkv"> + </ResponseProfile> + <ResponseProfile container="mkv" type="Video" mimeType="video/x-mkv"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index 5bd27c771..53b515e10 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -33,15 +33,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -65,5 +59,5 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml index f5502ca14..76b52c743 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml @@ -32,15 +32,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="mpeg2video" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="mpeg2video" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -71,27 +65,27 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg4,vc1" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> + <ResponseProfiles> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg4,vc1" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="avi" type="Video" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="avi" type="Video" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="mkv" type="Video" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="mkv" type="Video" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" type="Video" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" type="Video" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="mp4" type="Video" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mp4" type="Video" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="mpeg" type="Video" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mpeg" type="Video" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="mp3" type="Audio" mimeType="audio/mpeg"> + </ResponseProfile> + <ResponseProfile container="mp3" type="Audio" mimeType="audio/mpeg"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml index 1337b5936..8c41cc3b7 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -30,15 +30,9 @@ <DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -80,21 +74,21 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> + <ResponseProfiles> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml index b022c10a5..1ccd5f1de 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -33,15 +33,9 @@ <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -83,21 +77,21 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> + <ResponseProfiles> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml index cbef70b37..b578a98b6 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -35,15 +35,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -66,21 +60,21 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> + <ResponseProfiles> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml index 47db46ce1..fe5c63f90 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -40,15 +40,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -66,21 +60,21 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> + <ResponseProfiles> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> + </ResponseProfile> + <ResponseProfile container="ts" videoCodec="mpeg2video" type="Video" orgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO" mimeType="video/vnd.dlna.mpeg-tts"> <Conditions /> - </MediaProfile> - <MediaProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> + </ResponseProfile> + <ResponseProfile container="mpeg" videoCodec="mpeg1video,mpeg2video" type="Video" orgPn="MPEG_PS_NTSC,MPEG_PS_PAL" mimeType="video/mpeg"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml index d9aa441bb..f0db13e2a 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -29,15 +29,9 @@ <DirectPlayProfile container="avi,mp4" type="Video" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -80,15 +74,15 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="mp4,mov" audioCodec="aac" type="Video" mimeType="video/mp4"> + <ResponseProfiles> + <ResponseProfile container="mp4,mov" audioCodec="aac" type="Video" mimeType="video/mp4"> <Conditions /> - </MediaProfile> - <MediaProfile container="avi" type="Video" orgPn="AVI" mimeType="video/divx"> + </ResponseProfile> + <ResponseProfile container="avi" type="Video" orgPn="AVI" mimeType="video/divx"> <Conditions /> - </MediaProfile> - <MediaProfile container="wav" type="Audio" mimeType="audio/wav"> + </ResponseProfile> + <ResponseProfile container="wav" type="Audio" mimeType="audio/wav"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml index 0f4ad54a7..bebdb2b45 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml @@ -38,17 +38,9 @@ <DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" videoProfile="baseline" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Photo"> @@ -72,9 +64,9 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="ts" type="Video" orgPn="MPEG_TS_SD_NA"> + <ResponseProfiles> + <ResponseProfile container="ts" type="Video" orgPn="MPEG_TS_SD_NA"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml index 1e8d8164c..a2ac02ff9 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml @@ -33,17 +33,9 @@ <DirectPlayProfile container="jpeg" type="Photo" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="asf" type="Video" videoCodec="wmv2" audioCodec="wmav2" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Bytes"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> - <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="asf" type="Video" videoCodec="wmv2" audioCodec="wmav2" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Bytes" videoProfile="baseline" /> + <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles> <ContainerProfile type="Video" container="mp4,mov"> @@ -95,9 +87,9 @@ </Conditions> </CodecProfile> </CodecProfiles> - <MediaProfiles> - <MediaProfile container="avi" type="Video" mimeType="video/avi"> + <ResponseProfiles> + <ResponseProfile container="avi" type="Video" mimeType="video/avi"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml index f6c338b2f..aa2081dc0 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml @@ -24,18 +24,14 @@ <DirectPlayProfile container="mp3,wma" type="Audio" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> </TranscodingProfiles> <ContainerProfiles /> <CodecProfiles /> - <MediaProfiles> - <MediaProfile container="avi" type="Video" mimeType="video/x-msvideo"> + <ResponseProfiles> + <ResponseProfile container="avi" type="Video" mimeType="video/x-msvideo"> <Conditions /> - </MediaProfile> - </MediaProfiles> + </ResponseProfile> + </ResponseProfiles> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml index f682e4c42..02a1529f3 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml @@ -26,16 +26,10 @@ <DirectPlayProfile container="avi,mp4" type="Video" /> </DirectPlayProfiles> <TranscodingProfiles> - <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings /> - </TranscodingProfile> - <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto"> - <Settings> - <TranscodingSetting name="VideoProfile" value="baseline" /> - </Settings> - </TranscodingProfile> + <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" /> + <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" videoProfile="baseline" /> </TranscodingProfiles> <ContainerProfiles /> <CodecProfiles /> - <MediaProfiles /> + <ResponseProfiles /> </Profile>
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs new file mode 100644 index 000000000..08b7fbe49 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -0,0 +1,91 @@ +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()); + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs new file mode 100644 index 000000000..4a9023c7f --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -0,0 +1,235 @@ +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace MediaBrowser.MediaEncoding.Encoder +{ + public static class EncodingUtils + { + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + public static string GetInputArgument(List<string> inputFiles, bool isRemote) + { + if (isRemote) + { + return GetHttpInputArgument(inputFiles); + } + + return GetConcatInputArgument(inputFiles); + } + + /// <summary> + /// Gets the concat input argument. + /// </summary> + /// <param name="inputFiles">The input files.</param> + /// <returns>System.String.</returns> + private static string GetConcatInputArgument(List<string> inputFiles) + { + // Get all streams + // If there's more than one we'll need to use the concat command + if (inputFiles.Count > 1) + { + var files = string.Join("|", inputFiles); + + return string.Format("concat:\"{0}\"", files); + } + + // Determine the input path for video files + return GetFileInputArgument(inputFiles[0]); + } + + /// <summary> + /// Gets the file input argument. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>System.String.</returns> + private static string GetFileInputArgument(string path) + { + return string.Format("file:\"{0}\"", path); + } + + /// <summary> + /// Gets the HTTP input argument. + /// </summary> + /// <param name="inputFiles">The input files.</param> + /// <returns>System.String.</returns> + private static string GetHttpInputArgument(IEnumerable<string> inputFiles) + { + var url = inputFiles.First(); + + 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; + + if (time.HasValue) + { + var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds; + + if (seconds > 0) + { + return string.Format("-ss {0}", seconds.ToString(UsCulture)); + } + } + + return string.Empty; + } + + public static string GetProbeSizeArgument(bool isDvd) + { + 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> + /// <param name="request">The request.</param> + /// <param name="audioStream">The audio stream.</param> + /// <returns>System.Nullable{System.Int32}.</returns> + public static int? GetNumAudioChannelsParam(EncodingOptions request, MediaStream audioStream) + { + if (audioStream != null) + { + var codec = request.AudioCodec ?? string.Empty; + + if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) + { + // wmav2 currently only supports two channel output + return 2; + } + } + + if (request.MaxAudioChannels.HasValue) + { + if (audioStream != null && audioStream.Channels.HasValue) + { + return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value); + } + + return request.MaxAudioChannels.Value; + } + + 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, 1); + } + + 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 ? 2 : 0; + default: + throw new Exception("Unrecognized MediaEncodingQuality value."); + } + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs new file mode 100644 index 000000000..05733aef0 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs @@ -0,0 +1,168 @@ +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; + } + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs new file mode 100644 index 000000000..826525aef --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs @@ -0,0 +1,95 @@ +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() + { + + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs new file mode 100644 index 000000000..e6b67b0df --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs @@ -0,0 +1,311 @@ +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, state.AudioStream) : + 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; + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index fac54ecff..675d43be7 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -6,10 +6,10 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Concurrent; -using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -122,35 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <exception cref="System.ArgumentException">Unrecognized InputType</exception> public string GetInputArgument(string[] inputFiles, InputType type) { - string inputPath; - - switch (type) - { - case InputType.Bluray: - case InputType.Dvd: - case InputType.File: - inputPath = GetConcatInputArgument(inputFiles); - break; - case InputType.Url: - inputPath = GetHttpInputArgument(inputFiles); - break; - default: - throw new ArgumentException("Unrecognized InputType"); - } - - return inputPath; - } - - /// <summary> - /// Gets the HTTP input argument. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <returns>System.String.</returns> - private string GetHttpInputArgument(string[] inputFiles) - { - var url = inputFiles[0]; - - return string.Format("\"{0}\"", url); + return EncodingUtils.GetInputArgument(inputFiles.ToList(), type == InputType.Url); } /// <summary> @@ -160,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <returns>System.String.</returns> public string GetProbeSizeArgument(InputType type) { - return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty; + return EncodingUtils.GetProbeSizeArgument(type == InputType.Dvd); } /// <summary> @@ -796,7 +768,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=80\" -f image2 \"{1}\"", inputPath, "-", vf) : + var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=50\" -f image2 \"{1}\"", inputPath, "-", vf) : string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf); var probeSize = GetProbeSizeArgument(type); @@ -879,36 +851,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return memoryStream; } - /// <summary> - /// Gets the file input argument. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.String.</returns> - private string GetFileInputArgument(string path) - { - return string.Format("file:\"{0}\"", path); - } - - /// <summary> - /// Gets the concat input argument. - /// </summary> - /// <param name="playableStreamFiles">The playable stream files.</param> - /// <returns>System.String.</returns> - private string GetConcatInputArgument(string[] playableStreamFiles) - { - // Get all streams - // If there's more than one we'll need to use the concat command - if (playableStreamFiles.Length > 1) - { - var files = string.Join("|", playableStreamFiles); - - return string.Format("concat:\"{0}\"", files); - } - - // Determine the input path for video files - return GetFileInputArgument(playableStreamFiles[0]); - } - public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken) { return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken); diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index fb1041f89..ee1658ddd 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -48,7 +48,12 @@ </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" /> </ItemGroup> diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 6c4d9d9e2..00fb2131c 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -428,8 +428,8 @@ <Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs"> <Link>Session\BrowseRequest.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Session\GenericCommand.cs"> - <Link>Session\GenericCommand.cs</Link> + <Compile Include="..\MediaBrowser.Model\Session\GeneralCommand.cs"> + <Link>Session\GeneralCommand.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs"> <Link>Session\MessageCommand.cs</Link> @@ -449,9 +449,6 @@ <Compile Include="..\MediaBrowser.Model\Session\SessionInfoDto.cs"> <Link>Session\SessionInfoDto.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs"> - <Link>Session\SystemCommand.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs"> <Link>Session\UserDataChangeInfo.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index b39cecc61..f8bc2a600 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -415,8 +415,8 @@ <Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs"> <Link>Session\BrowseRequest.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Session\GenericCommand.cs"> - <Link>Session\GenericCommand.cs</Link> + <Compile Include="..\MediaBrowser.Model\Session\GeneralCommand.cs"> + <Link>Session\GeneralCommand.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs"> <Link>Session\MessageCommand.cs</Link> @@ -436,9 +436,6 @@ <Compile Include="..\MediaBrowser.Model\Session\SessionInfoDto.cs"> <Link>Session\SessionInfoDto.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs"> - <Link>Session\SystemCommand.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs"> <Link>Session\UserDataChangeInfo.cs</Link> </Compile> diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 28c5822e9..dc5e26be3 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -593,17 +593,9 @@ namespace MediaBrowser.Model.ApiClient /// Sends the command asynchronous. /// </summary> /// <param name="sessionId">The session identifier.</param> - /// <param name="request">The request.</param> - /// <returns>Task.</returns> - Task SendCommandAsync(string sessionId, GenericCommand request); - - /// <summary> - /// Sends a system command to the client - /// </summary> - /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <returns>Task.</returns> - Task SendSystemCommandAsync(string sessionId, SystemCommand command); + Task SendCommandAsync(string sessionId, GeneralCommand command); /// <summary> /// Instructs the client to display a message to the user diff --git a/MediaBrowser.Model/ApiClient/IServerEvents.cs b/MediaBrowser.Model/ApiClient/IServerEvents.cs index 0a38c63ad..e13f3cc2c 100644 --- a/MediaBrowser.Model/ApiClient/IServerEvents.cs +++ b/MediaBrowser.Model/ApiClient/IServerEvents.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Model.ApiClient /// <summary> /// Occurs when [system command]. /// </summary> - event EventHandler<SystemCommandEventArgs> SystemCommand; + event EventHandler<GeneralCommandEventArgs> GeneralCommand; /// <summary> /// Occurs when [notification added]. /// </summary> diff --git a/MediaBrowser.Model/ApiClient/ServerEventArgs.cs b/MediaBrowser.Model/ApiClient/ServerEventArgs.cs index d3212caf4..6637edd74 100644 --- a/MediaBrowser.Model/ApiClient/ServerEventArgs.cs +++ b/MediaBrowser.Model/ApiClient/ServerEventArgs.cs @@ -152,13 +152,19 @@ namespace MediaBrowser.Model.ApiClient /// <summary> /// Class SystemCommandEventArgs /// </summary> - public class SystemCommandEventArgs : EventArgs + public class GeneralCommandEventArgs : EventArgs { /// <summary> /// Gets or sets the command. /// </summary> /// <value>The command.</value> - public SystemCommand Command { get; set; } + public GeneralCommand Command { get; set; } + + /// <summary> + /// Gets or sets the type of the known command. + /// </summary> + /// <value>The type of the known command.</value> + public GeneralCommandType? KnownCommandType { get; set; } } /// <summary> diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index d0caa3ad2..932d5d63d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -218,6 +218,8 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } public string WanDdns { get; set; } + public string UICulture { get; set; } + public DlnaOptions DlnaOptions { get; set; } /// <summary> @@ -281,6 +283,8 @@ namespace MediaBrowser.Model.Configuration MetadataOptions = options.ToArray(); DlnaOptions = new DlnaOptions(); + + UICulture = "en-us"; } } diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs index 16aea8436..9f5f00d80 100644 --- a/MediaBrowser.Model/Globalization/CountryInfo.cs +++ b/MediaBrowser.Model/Globalization/CountryInfo.cs @@ -30,4 +30,10 @@ namespace MediaBrowser.Model.Globalization /// <value>The name of the three letter ISO region.</value> public string ThreeLetterISORegionName { get; set; } } + + public class LocalizatonOption + { + public string Name { get; set; } + public string Value { get; set; } + } } diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs index d17e2a7f9..fe6faf363 100644 --- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs @@ -33,6 +33,8 @@ namespace MediaBrowser.Model.LiveTv /// <value>The external identifier.</value> public string ExternalId { get; set; } + public List<MediaSourceInfo> MediaSources { get; set; } + /// <summary> /// Gets or sets the image tags. /// </summary> @@ -112,6 +114,7 @@ namespace MediaBrowser.Model.LiveTv public ChannelInfoDto() { ImageTags = new Dictionary<ImageType, Guid>(); + MediaSources = new List<MediaSourceInfo>(); } public event PropertyChangedEventHandler PropertyChanged; diff --git a/MediaBrowser.Model/LiveTv/ChannelQuery.cs b/MediaBrowser.Model/LiveTv/ChannelQuery.cs index eb3b20ce3..9079ebacd 100644 --- a/MediaBrowser.Model/LiveTv/ChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/ChannelQuery.cs @@ -13,6 +13,12 @@ namespace MediaBrowser.Model.LiveTv public ChannelType? ChannelType { get; set; } /// <summary> + /// Gets or sets a value indicating whether this instance is favorite. + /// </summary> + /// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value> + public bool? IsFavorite { get; set; } + + /// <summary> /// Gets or sets the user identifier. /// </summary> /// <value>The user identifier.</value> diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs index 40aa5710e..de07382c0 100644 --- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs @@ -1,11 +1,11 @@ -using System.Diagnostics; -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Library; using System; using System.Collections.Generic; using System.ComponentModel; -using MediaBrowser.Model.Library; +using System.Diagnostics; +using System.Runtime.Serialization; namespace MediaBrowser.Model.LiveTv { @@ -248,10 +248,13 @@ namespace MediaBrowser.Model.LiveTv /// <value>The type.</value> public string Type { get; set; } + public List<MediaSourceInfo> MediaSources { get; set; } + public RecordingInfoDto() { Genres = new List<string>(); ImageTags = new Dictionary<ImageType, Guid>(); + MediaSources = new List<MediaSourceInfo>(); } public event PropertyChangedEventHandler PropertyChanged; diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 207543fe8..0859f6999 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -132,7 +132,7 @@ <Compile Include="Querying\UserQuery.cs" /> <Compile Include="Search\SearchQuery.cs" /> <Compile Include="Session\BrowseRequest.cs" /> - <Compile Include="Session\GenericCommand.cs" /> + <Compile Include="Session\GeneralCommand.cs" /> <Compile Include="Session\MessageCommand.cs" /> <Compile Include="Session\PlaybackReports.cs" /> <Compile Include="Session\PlayRequest.cs" /> @@ -171,7 +171,6 @@ <Compile Include="Serialization\IXmlSerializer.cs" /> <Compile Include="Session\SessionCapabilities.cs" /> <Compile Include="Session\SessionInfoDto.cs" /> - <Compile Include="Session\SystemCommand.cs" /> <Compile Include="Session\UserDataChangeInfo.cs" /> <Compile Include="Themes\AppTheme.cs" /> <Compile Include="Themes\ThemeImage.cs" /> diff --git a/MediaBrowser.Model/Session/GenericCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs index f7ea0a84a..0de7d6dd8 100644 --- a/MediaBrowser.Model/Session/GenericCommand.cs +++ b/MediaBrowser.Model/Session/GeneralCommand.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace MediaBrowser.Model.Session { - public class GenericCommand + public class GeneralCommand { public string Name { get; set; } @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Session public Dictionary<string, string> Arguments { get; set; } - public GenericCommand() + public GeneralCommand() { Arguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } @@ -20,7 +20,7 @@ namespace MediaBrowser.Model.Session /// <summary> /// This exists simply to identify a set of known commands. /// </summary> - public enum CoreGenericCommand + public enum GeneralCommandType { MoveUp = 0, MoveDown = 1, diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index 949274a5d..74d7a70a3 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -39,14 +39,22 @@ namespace MediaBrowser.Model.Session /// <summary> /// The play now /// </summary> - PlayNow, + PlayNow = 0, /// <summary> /// The play next /// </summary> - PlayNext, + PlayNext = 1, /// <summary> /// The play last /// </summary> - PlayLast + PlayLast = 2, + /// <summary> + /// The play instant mix + /// </summary> + PlayInstantMix = 3, + /// <summary> + /// The play shuffle + /// </summary> + PlayShuffle = 4 } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Session/SystemCommand.cs b/MediaBrowser.Model/Session/SystemCommand.cs deleted file mode 100644 index 2fcaef6e3..000000000 --- a/MediaBrowser.Model/Session/SystemCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace MediaBrowser.Model.Session -{ - public enum SystemCommand - { - GoHome, - GoToSettings, - VolumeUp, - VolumeDown, - Mute, - Unmute, - ToggleMute - } -} diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index ec31c5529..5b1e7d6bb 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -117,6 +117,12 @@ namespace MediaBrowser.Model.System public string LogPath { get; set; } /// <summary> + /// Gets or sets the internal metadata path. + /// </summary> + /// <value>The internal metadata path.</value> + public string InternalMetadataPath { get; set; } + + /// <summary> /// Gets or sets the transcoding temporary path. /// </summary> /// <value>The transcoding temporary path.</value> diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index ff8f6a2e6..c33b9d549 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -174,11 +174,21 @@ namespace MediaBrowser.Providers.TV /// <returns>Task.</returns> private async Task<bool> AddMissingEpisodes(List<Series> series, string seriesDataPath, IEnumerable<Tuple<int, int>> episodeLookup, CancellationToken cancellationToken) { - var existingEpisodes = series.SelectMany(s => s.RecursiveChildren.OfType<Episode>()).ToList(); + var existingEpisodes = (from s in series + let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) + from c in s.RecursiveChildren.OfType<Episode>() + select new Tuple<int, Episode>((c.ParentIndexNumber ?? 0) + seasonOffset, c)) + .ToList(); + + var lookup = episodeLookup as IList<Tuple<int, int>> ?? episodeLookup.ToList(); + + var seasonCounts = (from e in lookup + group e by e.Item1 into g select g) + .ToDictionary(g => g.Key, g => g.Count()); var hasChanges = false; - foreach (var tuple in episodeLookup) + foreach (var tuple in lookup) { if (tuple.Item1 <= 0) { @@ -192,7 +202,7 @@ namespace MediaBrowser.Providers.TV continue; } - var existingEpisode = GetExistingEpisode(existingEpisodes, tuple); + var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple); if (existingEpisode != null) { @@ -208,13 +218,13 @@ namespace MediaBrowser.Providers.TV var now = DateTime.UtcNow; var targetSeries = DetermineAppropriateSeries(series, tuple.Item1); + var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(targetSeries.ProviderIds) ?? ((targetSeries.AnimeSeriesIndex ?? 1) - 1); if (airDate.Value < now) { // tvdb has a lot of nearly blank episodes _logger.Info("Creating virtual missing episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - - await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -222,8 +232,7 @@ namespace MediaBrowser.Providers.TV { // tvdb has a lot of nearly blank episodes _logger.Info("Creating virtual unaired episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - - await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -232,40 +241,48 @@ namespace MediaBrowser.Providers.TV return hasChanges; } - private Series DetermineAppropriateSeries(List<Series> series, int seasonNumber) + private Series DetermineAppropriateSeries(IEnumerable<Series> series, int seasonNumber) { - return series.FirstOrDefault(s => s.RecursiveChildren.OfType<Season>().Any(season => season.IndexNumber == seasonNumber)) ?? - series.FirstOrDefault(s => s.RecursiveChildren.OfType<Season>().Any(season => season.IndexNumber == 1)) ?? - series.OrderBy(s => s.RecursiveChildren.OfType<Season>().Select(season => season.IndexNumber).Min()).First(); - } + var seriesAndOffsets = series.Select(s => new { Series = s, SeasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) }).ToList(); + var bestMatch = seriesAndOffsets.FirstOrDefault(s => s.Series.RecursiveChildren.OfType<Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == seasonNumber)) ?? + seriesAndOffsets.FirstOrDefault(s => s.Series.RecursiveChildren.OfType<Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == 1)) ?? + seriesAndOffsets.OrderBy(s => s.Series.RecursiveChildren.OfType<Season>().Select(season => season.IndexNumber + s.SeasonOffset).Min()).First(); + + return bestMatch.Series; + } + /// <summary> /// Removes the virtual entry after a corresponding physical version has been added /// </summary> private async Task<bool> RemoveObsoleteOrMissingEpisodes(IEnumerable<Series> series, IEnumerable<Tuple<int, int>> episodeLookup, CancellationToken cancellationToken) { - var existingEpisodes = series.SelectMany(s => s.RecursiveChildren.OfType<Episode>()).ToList(); + var existingEpisodes = (from s in series + let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) + from c in s.RecursiveChildren.OfType<Episode>() + select new { SeasonOffset = seasonOffset, Episode = c }) + .ToList(); var physicalEpisodes = existingEpisodes - .Where(i => i.LocationType != LocationType.Virtual) + .Where(i => i.Episode.LocationType != LocationType.Virtual) .ToList(); var virtualEpisodes = existingEpisodes - .Where(i => i.LocationType == LocationType.Virtual) + .Where(i => i.Episode.LocationType == LocationType.Virtual) .ToList(); var episodesToRemove = virtualEpisodes .Where(i => { - if (i.IndexNumber.HasValue && i.ParentIndexNumber.HasValue) + if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue) { - var seasonNumber = i.ParentIndexNumber.Value; - var episodeNumber = i.IndexNumber.Value; + var seasonNumber = i.Episode.ParentIndexNumber.Value + i.SeasonOffset; + var episodeNumber = i.Episode.IndexNumber.Value; // If there's a physical episode with the same season and episode number, delete it if (physicalEpisodes.Any(p => - p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber && - p.ContainsEpisodeNumber(episodeNumber))) + p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value + p.SeasonOffset) == seasonNumber && + p.Episode.ContainsEpisodeNumber(episodeNumber))) { return true; } @@ -285,7 +302,7 @@ namespace MediaBrowser.Providers.TV var hasChanges = false; - foreach (var episodeToRemove in episodesToRemove) + foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode)) { _logger.Info("Removing missing/unaired episode {0} {1}x{2}", episodeToRemove.Series.Name, episodeToRemove.ParentIndexNumber, episodeToRemove.IndexNumber); @@ -306,25 +323,29 @@ namespace MediaBrowser.Providers.TV /// <returns>Task{System.Boolean}.</returns> private async Task<bool> RemoveObsoleteOrMissingSeasons(IEnumerable<Series> series, IEnumerable<Tuple<int, int>> episodeLookup, CancellationToken cancellationToken) { - var existingSeasons = series.SelectMany(s => s.Children.OfType<Season>()).ToList(); + var existingSeasons = (from s in series + let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) + from c in s.Children.OfType<Season>() + select new { SeasonOffset = seasonOffset, Season = c }) + .ToList(); var physicalSeasons = existingSeasons - .Where(i => i.LocationType != LocationType.Virtual) + .Where(i => i.Season.LocationType != LocationType.Virtual) .ToList(); var virtualSeasons = existingSeasons - .Where(i => i.LocationType == LocationType.Virtual) + .Where(i => i.Season.LocationType == LocationType.Virtual) .ToList(); var seasonsToRemove = virtualSeasons .Where(i => { - if (i.IndexNumber.HasValue) + if (i.Season.IndexNumber.HasValue) { - var seasonNumber = i.IndexNumber.Value; + var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset; // If there's a physical season with the same number, delete it - if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber)) + if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber)) { return true; } @@ -344,7 +365,7 @@ namespace MediaBrowser.Providers.TV var hasChanges = false; - foreach (var seasonToRemove in seasonsToRemove) + foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season)) { _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber); @@ -417,9 +438,7 @@ namespace MediaBrowser.Providers.TV await series.AddChild(season, cancellationToken).ConfigureAwait(false); - await season.RefreshMetadata(new MetadataRefreshOptions - { - }, cancellationToken).ConfigureAwait(false); + await season.RefreshMetadata(new MetadataRefreshOptions(), cancellationToken).ConfigureAwait(false); return season; } @@ -428,12 +447,37 @@ namespace MediaBrowser.Providers.TV /// Gets the existing episode. /// </summary> /// <param name="existingEpisodes">The existing episodes.</param> + /// <param name="seasonCounts"></param> /// <param name="tuple">The tuple.</param> /// <returns>Episode.</returns> - private Episode GetExistingEpisode(IEnumerable<Episode> existingEpisodes, Tuple<int, int> tuple) + private Episode GetExistingEpisode(IList<Tuple<int, Episode>> existingEpisodes, Dictionary<int, int> seasonCounts, Tuple<int, int> tuple) + { + var s = tuple.Item1; + var e = tuple.Item2; + + while (true) + { + var episode = GetExistingEpisode(existingEpisodes, s, e); + if (episode != null) + return episode; + + s--; + + if (seasonCounts.ContainsKey(s)) + e += seasonCounts[s]; + else + break; + } + + return null; + } + + private static Episode GetExistingEpisode(IEnumerable<Tuple<int, Episode>> existingEpisodes, int season, int episode) { return existingEpisodes - .FirstOrDefault(i => (i.ParentIndexNumber ?? -1) == tuple.Item1 && i.ContainsEpisodeNumber(tuple.Item2)); + .Where(i => i.Item1 == season && i.Item2.ContainsEpisodeNumber(episode)) + .Select(i => i.Item2) + .FirstOrDefault(); } /// <summary> diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs index 8c9b8672c..d350d2fe4 100644 --- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs +++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -11,6 +12,11 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { + class SeriesGroup : List<Series>, IGrouping<string, Series> + { + public string Key { get; set; } + } + class SeriesPostScanTask : ILibraryPostScanTask, IHasOrder { /// <summary> @@ -39,11 +45,7 @@ namespace MediaBrowser.Providers.TV .OfType<Series>() .ToList(); - var seriesGroups = from series in seriesList - let tvdbId = series.GetProviderId(MetadataProviders.Tvdb) - where !string.IsNullOrEmpty(tvdbId) - group series by tvdbId into g - select g; + var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList(); await new MissingEpisodeProvider(_logger, _config, _libraryManager).Run(seriesGroups, cancellationToken).ConfigureAwait(false); @@ -84,6 +86,51 @@ namespace MediaBrowser.Providers.TV } } + private IEnumerable<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList) + { + var links = seriesList.ToDictionary(s => s, s => seriesList.Where(c => c != s && ShareProviderId(s, c)).ToList()); + + var visited = new HashSet<Series>(); + + foreach (var series in seriesList) + { + if (!visited.Contains(series)) + { + var group = new SeriesGroup(); + FindAllLinked(series, visited, links, group); + + group.Key = group.Select(s => s.GetProviderId(MetadataProviders.Tvdb)).FirstOrDefault(id => !string.IsNullOrEmpty(id)); + + yield return group; + } + } + } + + private void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> results) + { + results.Add(series); + visited.Add(series); + + var links = linksMap[series]; + + foreach (var s in links) + { + if (!visited.Contains(s)) + { + FindAllLinked(s, visited, linksMap, results); + } + } + } + + private bool ShareProviderId(Series a, Series b) + { + return a.ProviderIds.Any(id => + { + string value; + return b.ProviderIds.TryGetValue(id.Key, out value) && id.Value == value; + }); + } + public int Order { get diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index a76e10111..f9ebaddf8 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -25,7 +25,8 @@ namespace MediaBrowser.Providers.TV { public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder { - internal const string TvdbSeriesOffset = "TvdbSeriesOffset"; + private const string TvdbSeriesOffset = "TvdbSeriesOffset"; + private const string TvdbSeriesOffsetFormat = "{0}-{1}"; internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2); internal static TvdbSeriesProvider Current { get; private set; } @@ -109,17 +110,22 @@ namespace MediaBrowser.Providers.TV return; var offset = info.AnimeSeriesIndex - index; - series.SetProviderId(TvdbSeriesOffset, offset.ToString()); + var id = string.Format(TvdbSeriesOffsetFormat, series.GetProviderId(MetadataProviders.Tvdb), offset); + series.SetProviderId(TvdbSeriesOffset, id); } internal static int? GetSeriesOffset(Dictionary<string, string> seriesProviderIds) { - string offsetString; - if (!seriesProviderIds.TryGetValue(TvdbSeriesOffset, out offsetString)) + string idString; + if (!seriesProviderIds.TryGetValue(TvdbSeriesOffset, out idString)) + return null; + + var parts = idString.Split('-'); + if (parts.Length < 2) return null; int offset; - if (int.TryParse(offsetString, out offset)) + if (int.TryParse(parts[1], out offset)) return offset; return null; diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 1833b708f..02da776bd 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; @@ -1064,7 +1065,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); } - dto.MediaSources = GetMediaSources(audio); + dto.MediaSources = GetAudioMediaSources(audio); dto.MediaSourceCount = 1; } @@ -1100,7 +1101,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.MediaSources)) { - dto.MediaSources = GetMediaSources(video); + dto.MediaSources = GetVideoMediaSources(video); } if (fields.Contains(ItemFields.Chapters)) @@ -1266,9 +1267,48 @@ namespace MediaBrowser.Server.Implementations.Dto { SetBookProperties(dto, book); } + + var tvChannel = item as LiveTvChannel; + + if (tvChannel != null) + { + dto.MediaSources = GetMediaSources(tvChannel); + } + } + + public List<MediaSourceInfo> GetMediaSources(BaseItem item) + { + var video = item as Video; + + if (video != null) + { + return GetVideoMediaSources(video); + } + + var audio = item as Audio; + + if (audio != null) + { + return GetAudioMediaSources(audio); + } + + var result = new List<MediaSourceInfo> + { + new MediaSourceInfo + { + Id = item.Id.ToString("N"), + LocationType = item.LocationType, + Name = item.Name, + Path = GetMappedPath(item), + MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList(), + RunTimeTicks = item.RunTimeTicks + } + }; + + return result; } - private List<MediaSourceInfo> GetMediaSources(Video item) + private List<MediaSourceInfo> GetVideoMediaSources(Video item) { var result = item.GetAlternateVersions().Select(GetVersionInfo).ToList(); @@ -1293,11 +1333,11 @@ namespace MediaBrowser.Server.Implementations.Dto .ToList(); } - private List<MediaSourceInfo> GetMediaSources(Audio item) + private List<MediaSourceInfo> GetAudioMediaSources(Audio item) { var result = new List<MediaSourceInfo> { - GetVersionInfo(item, true) + GetVersionInfo(item) }; return result; @@ -1321,7 +1361,7 @@ namespace MediaBrowser.Server.Implementations.Dto }; } - private MediaSourceInfo GetVersionInfo(Audio i, bool isPrimary) + private MediaSourceInfo GetVersionInfo(Audio i) { return new MediaSourceInfo { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 9279fd8d7..5b65a60c8 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -257,8 +257,12 @@ namespace MediaBrowser.Server.Implementations.IO InternalBufferSize = 32767 }; - newWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | - NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size; + newWatcher.NotifyFilter = NotifyFilters.CreationTime | + NotifyFilters.DirectoryName | + NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size | + NotifyFilters.Attributes; newWatcher.Created += watcher_Changed; newWatcher.Deleted += watcher_Changed; @@ -349,13 +353,13 @@ namespace MediaBrowser.Server.Implementations.IO { try { - Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); + Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath); ReportFileSystemChanged(e.FullPath); } catch (Exception ex) { - Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath); + Logger.ErrorException("Exception in ReportFileSystemChanged. Path: {0}", ex, e.FullPath); } } @@ -397,14 +401,6 @@ namespace MediaBrowser.Server.Implementations.IO Logger.Debug("Ignoring change to {0}", path); return true; } - - // Go up another level - parent = Path.GetDirectoryName(i); - if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Ignoring change to {0}", path); - return true; - } } return false; diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs new file mode 100644 index 000000000..9d5826454 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Library +{ + public class MusicManager : IMusicManager + { + private readonly ILibraryManager _libraryManager; + + public MusicManager(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user) + { + return GetInstantMixFromGenres(item.Genres, user); + } + + public IEnumerable<Audio> GetInstantMixFromArtist(string name, User user) + { + var artist = _libraryManager.GetArtist(name); + + var genres = _libraryManager.RootFolder + .RecursiveChildren + .OfType<Audio>() + .Where(i => i.HasArtist(name)) + .SelectMany(i => i.Genres) + .Concat(artist.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase); + + return GetInstantMixFromGenres(genres, user); + } + + public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user) + { + var genres = item + .RecursiveChildren + .OfType<Audio>() + .SelectMany(i => i.Genres) + .Concat(item.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase); + + return GetInstantMixFromGenres(genres, user); + } + + public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user) + { + var inputItems = user.RootFolder.GetRecursiveChildren(user); + + var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + + return inputItems + .OfType<Audio>() + .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey))) + .OrderByDescending(i => i.Item2) + .ThenBy(i => Guid.NewGuid()) + .Select(i => i.Item1) + .Take(100) + .OrderBy(i => Guid.NewGuid()); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 67c011a1f..b92e82385 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -34,8 +34,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { var collectionType = args.GetCollectionType(); - if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) || - string.IsNullOrEmpty(collectionType)) + if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) { return new Controller.Entities.Audio.Audio(); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 13a1e8f16..558087a25 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -115,22 +115,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } // Find movies that are mixed in the same folder - if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { return ResolveVideo<Trailer>(args); } Video item = null; - if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { item = ResolveVideo<MusicVideo>(args); } - if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { item = ResolveVideo<AdultVideo>(args); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 4e1bd860b..cf7358cce 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -222,13 +222,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv RunTimeTicks = (info.EndDate - info.StartDate).Ticks, OriginalAirDate = info.OriginalAirDate, - MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery - { - ItemId = recording.Id - - }).ToList() + MediaSources = _dtoService.GetMediaSources((BaseItem)recording) }; + dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList(); + if (info.Status == RecordingStatus.InProgress) { var now = DateTime.UtcNow.Ticks; @@ -317,7 +315,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Type = info.GetClientTypeName(), Id = info.Id.ToString("N"), MediaType = info.MediaType, - ExternalId = info.ExternalId + ExternalId = info.ExternalId, + MediaSources = _dtoService.GetMediaSources(info) }; if (user != null) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index db8786d62..b828dc0de 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -134,6 +134,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv return number; }); + + if (query.IsFavorite.HasValue) + { + var val = query.IsFavorite.Value; + + channels = channels + .Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val); + } } channels = channels.OrderBy(i => diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json new file mode 100644 index 000000000..12cdb7035 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Einstellungen gespeichert", + "AddUser": "Benutzer hinzuf\u00fcgen", + "Users": "Benutzer", + "Delete": "L\u00f6schen", + "Administrator": "Administrator", + "Password": "Passwort", + "CreatePassword": "Passwort erstellen", + "DeleteImage": "Bild l\u00f6schen", + "DeleteImageConfirmation": "M\u00f6chten Sie das Bild wirklich l\u00f6schen?", + "FileReadCancelled": "Das Einlesen der Datei wurde abgebrochen.", + "FileNotFound": "Datei nicht gefunden", + "FileReadError": "Beim Lesen der Datei ist ein Fehler aufgetreten.", + "DeleteUser": "Benutzer l\u00f6schen", + "DeleteUserConfirmation": "M\u00f6chten Sie {0} wirklich l\u00f6schen?", + "PasswordResetHeader": "Passwort zur\u00fccksetzen", + "PasswordResetComplete": "Das Passwort wurde zur\u00fcckgesetzt.", + "PasswordResetConfirmation": "M\u00f6chten Sie das Passwort wirklich zur\u00fccksetzen?", + "PasswordSaved": "Passwort gespeichert", + "PasswordMatchError": "Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein.", + "OptionOff": "Aus", + "OptionOn": "Ein", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Deinstalliere Plugin", + "UninstallPluginConfirmation": "M\u00f6chten Sie {0} wirklich deinstallieren?", + "NoPluginConfigurationMessage": "Bei diesem Plugin kann nichts eingestellt werden.", + "NoPluginsInstalledMessage": "Sie haben keine Plugins installiert.", + "BrowsePluginCatalogMessage": "Durchsuchen Sie unsere Bibliothek um alle verf\u00fcgbaren Plugins anzuzeigen." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json new file mode 100644 index 000000000..7184d4724 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Settings saved.", + "AddUser": "Add User", + "Users": "Users", + "Delete": "Delete", + "Administrator": "Administrator", + "Password": "Password", + "CreatePassword": "Create Password", + "DeleteImage": "Delete Image", + "DeleteImageConfirmation": "Are you sure you wish to delete this image?", + "FileReadCancelled": "The file read has been cancelled.", + "FileNotFound": "File not found.", + "FileReadError": "An error occurred while reading the file.", + "DeleteUser": "Delete User", + "DeleteUserConfirmation": "Are you sure you wish to delete {0}?", + "PasswordResetHeader": "Password Reset", + "PasswordResetComplete": "The password has been reset.", + "PasswordResetConfirmation": "Are you sure you wish to reset the password?", + "PasswordSaved": "Password saved.", + "PasswordMatchError": "Password and password confirmation must match.", + "OptionOff": "Off", + "OptionOn": "On", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Uninstall Plugin", + "UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?", + "NoPluginConfigurationMessage": "This plugin has nothing to configure.", + "NoPluginsInstalledMessage": "You have no plugins installed.", + "BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json new file mode 100644 index 000000000..b0b9fd1a9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Configuracion guardada", + "AddUser": "Agregar usuario", + "Users": "Usuarios", + "Delete": "Borrar", + "Administrator": "Administrador", + "Password": "Contrase\u00f1a", + "CreatePassword": "Crear Contrase\u00f1a", + "DeleteImage": "Borrar Imagen", + "DeleteImageConfirmation": "Esta seguro que desea borrar esta imagen?", + "FileReadCancelled": "La lectura del archivo se ha cancelado.", + "FileNotFound": "Archivo no encontrado.", + "FileReadError": "Se encontr\u00f3 un error al leer el archivo.", + "DeleteUser": "Borrar Usuario", + "DeleteUserConfirmation": "Esta seguro que desea eliminar a {0}?", + "PasswordResetHeader": "Restablecer contrase\u00f1a", + "PasswordResetComplete": "La contrase\u00f1a se ha restablecido.", + "PasswordResetConfirmation": "Esta seguro que desea restablecer la contrase\u00f1a?", + "PasswordSaved": "Contrase\u00f1a guardada.", + "PasswordMatchError": "La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.", + "OptionOff": "Apagado", + "OptionOn": "Prendido", + "OptionRelease": "Liberar", + "OptionBeta": "Beta", + "OptionDev": "Desarrollo", + "UninstallPluginHeader": "Desinstalar Plugin", + "UninstallPluginConfirmation": "Esta seguro que desea desinstalar {0}?", + "NoPluginConfigurationMessage": "El plugin no requiere configuraci\u00f3n", + "NoPluginsInstalledMessage": "No tiene plugins instalados.", + "BrowsePluginCatalogMessage": "Navegar el catalogo de plugins para ver los plugins disponibles." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json new file mode 100644 index 000000000..9e1ebbfee --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Param\u00e8tres sauvegard\u00e9s.", + "AddUser": "Ajout\u00e9 Usager", + "Users": "Usagers", + "Delete": "Supprimer", + "Administrator": "Administrateur", + "Password": "Mot de passe", + "CreatePassword": "Cr\u00e9er mot de passe", + "DeleteImage": "Supprimer Image", + "DeleteImageConfirmation": "\u00cates-vous s\u00fbr de vouloir supprimer l'image?", + "FileReadCancelled": "La lecture du fichier a \u00e9t\u00e9 annul\u00e9e.", + "FileNotFound": "Fichier non trouv\u00e9", + "FileReadError": "Un erreur est survenue pendant la lecture du fichier.", + "DeleteUser": "Supprimer Usager", + "DeleteUserConfirmation": "\u00cates-vous s\u00fbr de vouloir supprimer {0}?", + "PasswordResetHeader": "Red\u00e9marrage du mot de passe", + "PasswordResetComplete": "Le mot de passe a \u00e9t\u00e9 red\u00e9marr\u00e9.", + "PasswordResetConfirmation": "\u00cates-vous s\u00fbr de vouloir red\u00e9marrer le mot de passe?", + "PasswordSaved": "Mot de passe sauvegard\u00e9.", + "PasswordMatchError": "Mot de passe et confirmation de mot de passe doivent correspondre.", + "OptionOff": "Off", + "OptionOn": "On", + "OptionRelease": "Lancement", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "D\u00e9sinstaller module d'extention", + "UninstallPluginConfirmation": "\u00cates-vous s\u00fbr de vouloir d\u00e9sinstaller {0}?", + "NoPluginConfigurationMessage": "Ce module d'extension n'a rien \u00e0 configurer.", + "NoPluginsInstalledMessage": "Vous n'avez aucun module d'extension install\u00e9.", + "BrowsePluginCatalogMessage": "Explorer notre catalogue de modules d'extension pour voir ce qui est disponible." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json new file mode 100644 index 000000000..6b014bf34 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Settings saved.", + "AddUser": "Add User", + "Users": "Users", + "Delete": "Delete", + "Administrator": "Administrator", + "Password": "Password", + "CreatePassword": "Create Password", + "DeleteImage": "Delete Image", + "DeleteImageConfirmation": "Are you sure you wish to delete this image?", + "FileReadCancelled": "The file read has been cancelled.", + "FileNotFound": "File not found.", + "FileReadError": "An error occurred while reading the file.", + "DeleteUser": "Delete User", + "DeleteUserConfirmation": "Are you sure you wish to delete {0}?", + "PasswordResetHeader": "Password Reset", + "PasswordResetComplete": "The password has been reset.", + "PasswordResetConfirmation": "Are you sure you wish to reset the password?", + "PasswordSaved": "Password saved.", + "PasswordMatchError": "Password and password confirmation must match.", + "OptionOff": "Off", + "OptionOn": "On", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Uninstall Plugin", + "UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?", + "NoPluginConfigurationMessage": "This plugin has nothing to configure.", + "NoPluginsInstalledMessage": "You have no plugins installed.", + "BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json new file mode 100644 index 000000000..17a343283 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Instellingen opgeslagen.", + "AddUser": "Gebruiker toevoegen", + "Users": "Gebruikers", + "Delete": "Verwijderen", + "Administrator": "Beheerder", + "Password": "Wachtwoord", + "CreatePassword": "Maak wachtwoord", + "DeleteImage": "Verwijder afbeelding", + "DeleteImageConfirmation": "Weet je zeker dat je deze afbeelding wilt verwijderen?", + "FileReadCancelled": "Het lezen van het bestand is geannuleerd", + "FileNotFound": "Bestand niet gevonden.", + "FileReadError": "Er is een fout opgetreden bij het lezen van het bestand.", + "DeleteUser": "Verwijder gebruiker", + "DeleteUserConfirmation": "Weet je zeker dat je {0} wilt verwijderen?", + "PasswordResetHeader": "Wachtwoord opnieuw instellen", + "PasswordResetComplete": "Het wachtwoord is opnieuw ingesteld.", + "PasswordResetConfirmation": "Weet je zeker dat je het wachtwoord opnieuw in wilt stellen?", + "PasswordSaved": "Wachtwoord opgeslagen.", + "PasswordMatchError": "Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.", + "OptionOff": "Uit", + "OptionOn": "Aan", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Deinstalleer Plugin", + "UninstallPluginConfirmation": "Weet u zeker dat u {0} wilt deinstalleren?", + "NoPluginConfigurationMessage": "Deze plugin heeft niets in te stellen", + "NoPluginsInstalledMessage": "U heeft geen plugins geinstalleerd", + "BrowsePluginCatalogMessage": "Blader door de Plugincatalogus voor beschikbare plugins." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json new file mode 100644 index 000000000..73734c0c6 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Prefer\u00eancias salvas.", + "AddUser": "Adicionar Usu\u00e1rio", + "Users": "Usu\u00e1rios", + "Delete": "Apagar", + "Administrator": "Administrador", + "Password": "Senha", + "CreatePassword": "Criar Senha", + "DeleteImage": "Apagar Imagem", + "DeleteImageConfirmation": "Tem certeza que deseja apagar esta imagem?", + "FileReadCancelled": "A leitura do arquivo foi cancelada.", + "FileNotFound": "Arquivo n\u00e3o encontrado.", + "FileReadError": "Ocorreu um erro ao ler o arquivo.", + "DeleteUser": "Apagar Usu\u00e1rio", + "DeleteUserConfirmation": "Tem certeza que deseja apagar {0}?", + "PasswordResetHeader": "Redefinir Senha", + "PasswordResetComplete": "A senha foi redefinida.", + "PasswordResetConfirmation": "Deseja realmente redefinir a senha?", + "PasswordSaved": "Senha salva.", + "PasswordMatchError": "A senha e confirma\u00e7\u00e3o da senha devem conferir.", + "OptionOff": "Off", + "OptionOn": "On", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Desintalar Plugin", + "UninstallPluginConfirmation": "Deseja realmente desinstalar {0}?", + "NoPluginConfigurationMessage": "Este plugin n\u00e3o necessita configurar.", + "NoPluginsInstalledMessage": "N\u00e3o existem plugins instalados.", + "BrowsePluginCatalogMessage": "Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json new file mode 100644 index 000000000..124564499 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "Configura\u00e7\u00f5es guardadas.", + "AddUser": "Adicionar Utilizador", + "Users": "Utilizadores", + "Delete": "Apagar", + "Administrator": "Administrador", + "Password": "Senha", + "CreatePassword": "Criar Senha", + "DeleteImage": "Apagar Imagem", + "DeleteImageConfirmation": "Tem a certeza que pretende apagar a imagem?", + "FileReadCancelled": "A leitura do ficheiro foi cancelada.", + "FileNotFound": "Ficheiro n\u00e3o encontrado", + "FileReadError": "Ocorreu um erro ao ler o ficheiro.", + "DeleteUser": "Apagar Utilizador", + "DeleteUserConfirmation": "Tem a certeza que pretende apagar {0}?", + "PasswordResetHeader": "Redefinir Senha", + "PasswordResetComplete": "A senha foi redefinida.", + "PasswordResetConfirmation": "Tem a certeza que pretende redefinir a senha?", + "PasswordSaved": "Senha guardada.", + "PasswordMatchError": "A senha e a confirma\u00e7\u00e3o da senha devem coincidir.", + "OptionOff": "Desligado", + "OptionOn": "Ligado", + "OptionRelease": "Final", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Desinstalar extens\u00e3o", + "UninstallPluginConfirmation": "Tem a certeza que pretende desinstalar {0}?", + "NoPluginConfigurationMessage": "Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.", + "NoPluginsInstalledMessage": "N\u00e3o tem extens\u00f5es instaladas.", + "BrowsePluginCatalogMessage": "Navegue o nosso cat\u00e1logo de extens\u00f5es para ver as extens\u00f5es dispon\u00edveis." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json new file mode 100644 index 000000000..2657b9688 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b", + "AddUser": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "Users": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", + "Delete": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c", + "Administrator": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440", + "Password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "CreatePassword": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c", + "DeleteImage": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435", + "DeleteImageConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?", + "FileReadCancelled": "\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e", + "FileNotFound": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", + "FileReadError": "\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430", + "DeleteUser": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "DeleteUserConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?", + "PasswordResetHeader": "\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f", + "PasswordResetComplete": "\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d", + "PasswordResetConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?", + "PasswordSaved": "\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d", + "PasswordMatchError": "\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c", + "OptionOff": "\u0412\u044b\u043a\u043b.", + "OptionOn": "\u0412\u043a\u043b.", + "OptionRelease": "\u0412\u044b\u043f\u0443\u0441\u043a", + "OptionBeta": "\u0411\u0435\u0442\u0430", + "OptionDev": "\u0420\u0430\u0437\u0440\u0430\u0431.", + "UninstallPluginHeader": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d", + "UninstallPluginConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?", + "NoPluginConfigurationMessage": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a", + "NoPluginsInstalledMessage": "\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.", + "BrowsePluginCatalogMessage": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json new file mode 100644 index 000000000..ca7b3a7c7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -0,0 +1,31 @@ +{ + "SettingsSaved": "\u8a2d\u7f6e\u5df2\u4fdd\u5b58", + "AddUser": "Add User", + "Users": "\u7528\u6236", + "Delete": "\u522a\u9664", + "Administrator": "\u7ba1\u7406\u54e1", + "Password": "\u5bc6\u78bc", + "CreatePassword": "\u5275\u5efa\u5bc6\u78bc", + "DeleteImage": "\u522a\u9664\u5716\u50cf", + "DeleteImageConfirmation": "\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u9019\u5f35\u5716\u7247\uff1f", + "FileReadCancelled": "The file read has been cancelled.", + "FileNotFound": "File not found.", + "FileReadError": "An error occurred while reading the file.", + "DeleteUser": "\u522a\u9664\u7528\u6236", + "DeleteUserConfirmation": "Are you sure you wish to delete {0}?", + "PasswordResetHeader": "\u91cd\u8a2d\u5bc6\u78bc", + "PasswordResetComplete": "\u5bc6\u78bc\u5df2\u91cd\u8a2d", + "PasswordResetConfirmation": "\u4f60\u78ba\u5b9a\u8981\u91cd\u8a2d\u5bc6\u78bc\uff1f", + "PasswordSaved": "\u5bc6\u78bc\u5df2\u4fdd\u5b58\u3002", + "PasswordMatchError": "\u5bc6\u78bc\u548c\u78ba\u8a8d\u5bc6\u78bc\u5fc5\u9808\u4e00\u81f4\u3002", + "OptionOff": "Off", + "OptionOn": "On", + "OptionRelease": "Release", + "OptionBeta": "Beta", + "OptionDev": "Dev", + "UninstallPluginHeader": "Uninstall Plugin", + "UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?", + "NoPluginConfigurationMessage": "This plugin has nothing to configure.", + "NoPluginsInstalledMessage": "You have no plugins installed.", + "BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs index 0d428742c..9b907ef74 100644 --- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs +++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs @@ -1,9 +1,10 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Localization; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Serialization; using MoreLinq; using System; using System.Collections.Concurrent; @@ -11,6 +12,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; namespace MediaBrowser.Server.Implementations.Localization { @@ -33,15 +35,17 @@ namespace MediaBrowser.Server.Implementations.Localization new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase); private readonly IFileSystem _fileSystem; - + private readonly IJsonSerializer _jsonSerializer; + /// <summary> /// Initializes a new instance of the <see cref="LocalizationManager"/> class. /// </summary> /// <param name="configurationManager">The configuration manager.</param> - public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem) + public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _configurationManager = configurationManager; _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; ExtractAll(); } @@ -241,5 +245,117 @@ namespace MediaBrowser.Server.Implementations.Localization return value == null ? (int?)null : value.Value; } + + public string GetLocalizedString(string phrase) + { + return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture); + } + + public string GetLocalizedString(string phrase, string culture) + { + var dictionary = GetLocalizationDictionary(culture); + + string value; + + if (dictionary.TryGetValue(phrase, out value)) + { + return value; + } + + return phrase; + } + + private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries = + new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); + + public Dictionary<string, string> GetLocalizationDictionary(string culture) + { + const string prefix = "Server"; + var key = prefix + culture; + + return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "server.json")); + } + + public Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture) + { + const string prefix = "JavaScript"; + var key = prefix + culture; + + return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "javascript.json")); + } + + private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename) + { + var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + var assembly = GetType().Assembly; + var namespaceName = GetType().Namespace + "." + prefix; + + CopyInto(dictionary, namespaceName + "." + baseFilename, assembly); + CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture), assembly); + + return dictionary; + } + + private void CopyInto(IDictionary<string, string> dictionary, string resourcePath, Assembly assembly) + { + using (var stream = assembly.GetManifestResourceStream(resourcePath)) + { + if (stream != null) + { + var dict = _jsonSerializer.DeserializeFromStream<Dictionary<string, string>>(stream); + + foreach (var key in dict.Keys) + { + dictionary[key] = dict[key]; + } + } + } + } + + private string GetResourceFilename(string culture) + { + var parts = culture.Split('-'); + + if (parts.Length == 2) + { + culture = parts[0].ToLower() + "_" + parts[1].ToUpper(); + } + else + { + culture = culture.ToLower(); + } + + return culture + ".json"; + } + + public IEnumerable<LocalizatonOption> GetLocalizationOptions() + { + return new List<LocalizatonOption> + { + new LocalizatonOption{ Name="English (United States)", Value="en-us"}, + new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"}, + new LocalizatonOption{ Name="Dutch", Value="nl"}, + new LocalizatonOption{ Name="French", Value="fr"}, + new LocalizatonOption{ Name="German", Value="de"}, + new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"}, + new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"}, + new LocalizatonOption{ Name="Russian", Value="ru"}, + new LocalizatonOption{ Name="Spanish", Value="es"} + + }.OrderBy(i => i.Name); + } + + public string LocalizeDocument(string document, string culture, Func<string,string> tokenBuilder) + { + foreach (var pair in GetLocalizationDictionary(culture).ToList()) + { + var token = tokenBuilder(pair.Key); + + document = document.Replace(token, pair.Value, StringComparison.Ordinal); + } + + return document; + } } } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json new file mode 100644 index 000000000..36d6993d8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Ende", + "LabelVisitCommunity": "Besuche die Community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "Zeige API Dokumentation", + "LabelBrowseLibrary": "Durchsuche Bibliothek", + "LabelConfigureMediaBrowser": "Konfiguriere Media Browser", + "LabelOpenLibraryViewer": "\u00d6ffne Bibliothekenansicht", + "LabelRestartServer": "Server neustarten", + "LabelShowLogWindow": "Zeige Log Fenster", + "LabelPrevious": "Vorheriges", + "LabelFinish": "Ende", + "LabelNext": "N\u00e4chstes", + "LabelYoureDone": "Du bist fertig!", + "WelcomeToMediaBrowser": "Willkommen zu Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "Dieser Assistent wird Sie durch den Einrichtungsprozess f\u00fchren.", + "TellUsAboutYourself": "Sagen Sie uns etwas \u00fcber sich selbst", + "LabelYourFirstName": "Ihr Vorname:", + "MoreUsersCanBeAddedLater": "Weitere Benutzer k\u00f6nnen Sie sp\u00e4ter im Dashboard hinzuf\u00fcgen.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "Ein Windows Service wurde installiert.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Konfiguriere Einstellungen", + "LabelEnableVideoImageExtraction": "Aktiviere Videobild-Extrahierung", + "VideoImageExtractionHelp": "F\u00fcr Videos die noch keien Bilder haben, und f\u00fcr die wir keine Internetbilder finden k\u00f6nnen. Hierdurch wird der erste Bibliothekenscan etwas mehr Zeit beanspruchen, f\u00fchrt aber zu einer ansprechenderen Pr\u00e4sentation.", + "LabelEnableChapterImageExtractionForMovies": "Extrahiere Kapitelbilder f\u00fcr Filme", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Aktiviere automatische Portweiterleitung", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Abbrechen", + "HeaderSetupLibrary": "Medienbibliothek einrichten", + "ButtonAddMediaFolder": "Medienordner hinzuf\u00fcgen", + "LabelFolderType": "Ordnertyp:", + "MediaFolderHelpPluginRequired": "* Ben\u00f6tigt ein Plugin, wie GameBrowser oder MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Land:", + "LabelLanguage": "Sprache:", + "HeaderPreferredMetadataLanguage": "Bevorzugte Metadata Sprache:", + "LabelSaveLocalMetadata": "Speichere Bildmaterial und Metadaten in den Medienodnern", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Lade Bildmaterial und Metadaten aus dem Internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json new file mode 100644 index 000000000..40269bde3 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Exit", + "LabelVisitCommunity": "Visit Community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "View Api Documentation", + "LabelBrowseLibrary": "Browse Library", + "LabelConfigureMediaBrowser": "Configure Media Browser", + "LabelOpenLibraryViewer": "Open Library Viewer", + "LabelRestartServer": "Restart Server", + "LabelShowLogWindow": "Show Log Window", + "LabelPrevious": "Previous", + "LabelFinish": "Finish", + "LabelNext": "Next", + "LabelYoureDone": "You're Done!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json new file mode 100644 index 000000000..f475e1db4 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Salir", + "LabelVisitCommunity": "Visitar la comunidad", + "LabelGithubWiki": "Wiki de Github", + "LabelSwagger": "Swagger", + "LabelStandard": "Estandar", + "LabelViewApiDocumentation": "Ver documentacion de Api", + "LabelBrowseLibrary": "Navegar biblioteca", + "LabelConfigureMediaBrowser": "Configurar Media Browser", + "LabelOpenLibraryViewer": "Abrir el visor de la biblioteca", + "LabelRestartServer": "reiniciar el servidor", + "LabelShowLogWindow": "Mostrar la ventana del log", + "LabelPrevious": "Anterior", + "LabelFinish": "Terminar", + "LabelNext": "Siguiente", + "LabelYoureDone": "Ha Terminado!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json new file mode 100644 index 000000000..c2934807c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Quitter", + "LabelVisitCommunity": "Visiter Communaut\u00e9", + "LabelGithubWiki": "GitHub Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "Consulter la documentation API", + "LabelBrowseLibrary": "Naviguer la biblioth\u00e8que", + "LabelConfigureMediaBrowser": "Configurer Media Browser", + "LabelOpenLibraryViewer": "Ouvrir le navigateur de biblioth\u00e8que", + "LabelRestartServer": "Red\u00e9marrer Serveur", + "LabelShowLogWindow": "Afficher la fen\u00eatre du journal d'\u00e9v\u00e8nements", + "LabelPrevious": "Pr\u00e9c\u00e9dant", + "LabelFinish": "Termin\u00e9", + "LabelNext": "Suivant", + "LabelYoureDone": "Vous avez Termin\u00e9!", + "WelcomeToMediaBrowser": "Bienvenue \u00e0 Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "Cet assistant vous guidera dans le processus de configuration.", + "TellUsAboutYourself": "Parlez-nous de vous", + "LabelYourFirstName": "Votre pr\u00e9nom:", + "MoreUsersCanBeAddedLater": "Plus d'usagers pourrons \u00eatre ajout\u00e9s ult\u00e9rieurement \u00e0 partir du tableau de bord.", + "UserProfilesIntro": "Media Browser supporte nativement les profiles d'usager, donnant la possibilit\u00e9 pour chaque usager d'avoir ses propres param\u00e8tres d'affichage, \u00e9tats de lecture et param\u00e8tres de contr\u00f4le parental.", + "LabelWindowsService": "Service Windows", + "AWindowsServiceHasBeenInstalled": "Un service Windows a \u00e9t\u00e9 install\u00e9.", + "WindowsServiceIntro1": "Media Browser, normalement, fonctionne en tant qu'application de bureau avec un ic\u00f4ne de barre de t\u00e2ches, mais si vous pr\u00e9f\u00e9rez son fonctionnement en tant que service d'arri\u00e8re-plan, il peut \u00eatre d\u00e9marr\u00e9 par le gestionnaire de service Windows.", + "WindowsServiceIntro2": "Si le service Windows est utilis\u00e9, veuillez noter qu'il ne peut pas fonctionner en m\u00eame temps que l'application par la barre de t\u00e2che, donc il faut fermer l'application de la barre de t\u00e2ches pour pouvoir ex\u00e9cuter le service. Le service devra aussi \u00eatre configur\u00e9 avec les droits administratifs par le panneau de configuration. Veuillez noter qu'actuellement les mise \u00e0 jour automatique par le service ne sont pas stables, donc les mises \u00e0 jour devront se faire par interaction manuelle.", + "WizardCompleted": "C'est tout ce dont nous avons besoins pour l'instant. Media Browser a commenc\u00e9 \u00e0 ramasser l'information sur votre biblioth\u00e8que de m\u00e9dia. Visiter quelques unes de nos applications, ensuite cliquer <b>Termin\u00e9<\/b> pour voir le <b>Tableau de bord<\/b>", + "LabelConfigureSettings": "Configurer param\u00e8tres", + "LabelEnableVideoImageExtraction": "Activer l'extraction d'image des videos", + "VideoImageExtractionHelp": "Pour les vid\u00e9os sans images et que nous n'avons pas trouv\u00e9 par Internet. Ce processus prolongera la mise \u00e0 jour initiale de biblioth\u00e8que mais offrira une meilleure pr\u00e9sentation visuelle.", + "LabelEnableChapterImageExtractionForMovies": "Extraire les images de chapitre pour les films", + "LabelChapterImageExtractionForMoviesHelp": "L'extraction d'images de chapitre permettra aux clients d'afficher des menus graphiques des sc\u00e8nes. Le processus peut \u00eatre long et exigeant en ressource processeur et de stockage (plusieurs Gigabytes). Il s'ex\u00e9cute par d\u00e9faut dans les t\u00e2ches programm\u00e9es \u00e0 4:00 AM mais peut \u00eatre modifi\u00e9 dans les options de t\u00e2ches programm\u00e9es. Il n'est pas recommand\u00e9 d'ex\u00e9cuter cette t\u00e2che dans les heures d'utilisation standard.", + "LabelEnableAutomaticPortMapping": "Activer la configuration automatique de port", + "LabelEnableAutomaticPortMappingHelp": "UPnP permet la configuration automatique de routeur pour un acc\u00e8s distance facile. Ceci peut ne pas fonctionner sur certains mod\u00e8les de routeur.", + "ButtonOk": "Ok", + "ButtonCancel": "Annuler", + "HeaderSetupLibrary": "Configurer votre biblioth\u00e8que de m\u00e9dia", + "ButtonAddMediaFolder": "Ajouter r\u00e9pertoire de m\u00e9dia", + "LabelFolderType": "Type de r\u00e9pertoire:", + "MediaFolderHelpPluginRequired": "* Requiert l'utilisation d'un plug-in, Ex: GameBrowser ou MB BookShelf", + "ReferToMediaLibraryWiki": "Se r\u00e9f\u00e9rer au wiki des biblioth\u00e8ques de m\u00e9dia", + "LabelCountry": "Pays:", + "LabelLanguage": "Langue:", + "HeaderPreferredMetadataLanguage": "Langue de m\u00e9tadonn\u00e9es pr\u00e9f\u00e9r\u00e9e:", + "LabelSaveLocalMetadata": "Enregistrer les images et m\u00e9tadonn\u00e9es dans les r\u00e9pertoires de m\u00e9dia", + "LabelSaveLocalMetadataHelp": "Enregistrer les images et m\u00e9tadonn\u00e9es dans les r\u00e9pertoires de m\u00e9dia va les placer \u00e0 un endroit o\u00f9 elles pourront facilement \u00eatre modifi\u00e9es.", + "LabelDownloadInternetMetadata": "T\u00e9l\u00e9charger les images et m\u00e9tadonn\u00e9es de Internet", + "LabelDownloadInternetMetadataHelp": "Media Browser peut t\u00e9l\u00e9charger l'information \u00e0 propos de votre m\u00e9dia pour offrir une pr\u00e9sentation plus riche." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json new file mode 100644 index 000000000..0fcf20a53 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Afsluiten", + "LabelVisitCommunity": "Bezoek de community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standaard", + "LabelViewApiDocumentation": "Bekijk Api documentatie", + "LabelBrowseLibrary": "Bekijk bibliotheek", + "LabelConfigureMediaBrowser": "Configureer Media Browser", + "LabelOpenLibraryViewer": "Open bibliotheek verkenner", + "LabelRestartServer": "Herstart server", + "LabelShowLogWindow": "Toon log venster", + "LabelPrevious": "Vorige", + "LabelFinish": "Finish", + "LabelNext": "Volgende", + "LabelYoureDone": "Gereed!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json new file mode 100644 index 000000000..0ea78b956 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Sair", + "LabelVisitCommunity": "Visite o Community", + "LabelGithubWiki": "Wiki do Github", + "LabelSwagger": "Swagger", + "LabelStandard": "Padr\u00e3o", + "LabelViewApiDocumentation": "Ver documenta\u00e7\u00e3o da Api", + "LabelBrowseLibrary": "Navegar pela Biblioteca", + "LabelConfigureMediaBrowser": "Configurar Media Browser", + "LabelOpenLibraryViewer": "Abrir Visualizador da Biblioteca", + "LabelRestartServer": "Reiniciar Servidor", + "LabelShowLogWindow": "Mostrar Janela de Log", + "LabelPrevious": "Anterior", + "LabelFinish": "Terminar", + "LabelNext": "Pr\u00f3ximo", + "LabelYoureDone": "Pronto!", + "WelcomeToMediaBrowser": "Bem vindo ao Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "Este assistente ir\u00e1 gui\u00e1-lo pelo processo de instala\u00e7\u00e3o.", + "TellUsAboutYourself": "Conte-nos sobre voc\u00ea", + "LabelYourFirstName": "Seu primeiro nome:", + "MoreUsersCanBeAddedLater": "Mais usu\u00e1rios podem ser adicionados dentro do Painel.", + "UserProfilesIntro": "Media Browser inclui suporte a perfis de usu\u00e1rios, permitindo a cada usu\u00e1rio ter suas prefer\u00eancias de visualiza\u00e7\u00e3o, status das reprodu\u00e7\u00f5es e controle dos pais.", + "LabelWindowsService": "Servi\u00e7o do Windows", + "AWindowsServiceHasBeenInstalled": "Foi instalado um Servi\u00e7o do Windows.", + "WindowsServiceIntro1": "O Servidor Media Browser normalmente \u00e9 executado como uma aplica\u00e7\u00e3o de desktop com um \u00edcone na bandeja do sistema, mas se preferir executar como servi\u00e7o pode inici\u00e1-lo no painel de controle de servi\u00e7os do Windows", + "WindowsServiceIntro2": "Se usar o servi\u00e7o do Windows, por favor certifique-se que n\u00e3o esteja sendo executado ao mesmo tempo que o \u00edcone na bandeja, se for ter\u00e1 que sair da app antes de executar o servi\u00e7o. O servi\u00e7o necessita ser configurado com privil\u00e9gios de administrador no painel de controle. Neste momento o servi\u00e7o n\u00e3o pode se auto-atualizar, por isso novas vers\u00f5es exigir\u00e3o intera\u00e7\u00e3o manual.", + "WizardCompleted": "Isto \u00e9 todo o necess\u00e1rio. Media Browser iniciou a coleta das informa\u00e7\u00f5es de sua biblioteca de m\u00eddia. Conhe\u00e7a algumas de nossas apps e clique <b>Terminar<\/b> para ver o <b>Painel<\/b>.", + "LabelConfigureSettings": "Configurar prefer\u00eancias", + "LabelEnableVideoImageExtraction": "Ativar extra\u00e7\u00e3o de imagens de v\u00eddeo", + "VideoImageExtractionHelp": "Para v\u00eddeos que n\u00e3o tenham imagens e que n\u00e3o possamos encontrar imagens na internet. Isto aumentar\u00e1 o tempo do rastreamento inicial da biblioteca mas resultar\u00e1 em uma apresenta\u00e7\u00e3o mais bonita.", + "LabelEnableChapterImageExtractionForMovies": "Extrair imagens de cap\u00edtulos dos Filmes", + "LabelChapterImageExtractionForMoviesHelp": "Extrair imagens de cap\u00edtulos permitir\u00e1 aos clientes mostrar menus gr\u00e1ficos de sele\u00e7\u00e3o de cenas. O processo pode ser lento, uso intenso de cpu e muito espa\u00e7o em disco. Ele executa como uma tarefa di\u00e1ria noturna \u00e0s 4:00hs, embora seja configur\u00e1vel na \u00e1rea de tarefas agendadas. N\u00e3o \u00e9 recomendado executar durante as horas de pico de uso.", + "LabelEnableAutomaticPortMapping": "Ativar mapeamento de porta autom\u00e1tico", + "LabelEnableAutomaticPortMappingHelp": "UPnP permite uma configura\u00e7\u00e3o automatizada do roteador para acesso remoto f\u00e1cil. Isto pode n\u00e3o funcionar em alguns modelos de roteadores.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancelar", + "HeaderSetupLibrary": "Configurar sua biblioteca de m\u00eddias", + "ButtonAddMediaFolder": "Adicionar pasta de m\u00eddias", + "LabelFolderType": "Tipo de pasta:", + "MediaFolderHelpPluginRequired": "* Requer o uso de um plugin, ex. GameBrowser ou MB Bookshelf.", + "ReferToMediaLibraryWiki": "Consultar wiki da biblioteca de m\u00eddias", + "LabelCountry": "Pa\u00eds:", + "LabelLanguage": "Idioma:", + "HeaderPreferredMetadataLanguage": "Idioma preferido dos metadados:", + "LabelSaveLocalMetadata": "Salvar artwork e metadados dentro das pastas da m\u00eddia", + "LabelSaveLocalMetadataHelp": "Salvar artwork e metadados diretamente nas pastas da m\u00eddia, as deixar\u00e1 em um local f\u00e1cil para edit\u00e1-las.", + "LabelDownloadInternetMetadata": "Baixar artwork e metadados da internet", + "LabelDownloadInternetMetadataHelp": "Media Browser pode baixar informa\u00e7\u00f5es sobre sua m\u00eddia para melhorar a apresenta\u00e7\u00e3o." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json new file mode 100644 index 000000000..9fddc4f98 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Sair", + "LabelVisitCommunity": "Visitar a Comunidade", + "LabelGithubWiki": "Wiki Github", + "LabelSwagger": "Swagger", + "LabelStandard": "Padr\u00e3o", + "LabelViewApiDocumentation": "Ver Documenta\u00e7\u00e3o da API", + "LabelBrowseLibrary": "Navegar na Biblioteca", + "LabelConfigureMediaBrowser": "Configurar Media Browser", + "LabelOpenLibraryViewer": "Abrir Visualizador da Biblioteca", + "LabelRestartServer": "Reiniciar Servidor", + "LabelShowLogWindow": "Mostrar Janela de Log", + "LabelPrevious": "Anterior", + "LabelFinish": "Terminar", + "LabelNext": "Seguinte", + "LabelYoureDone": "Concluiu!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json new file mode 100644 index 000000000..076740f3c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "\u0412\u044b\u0445\u043e\u0434", + "LabelVisitCommunity": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c \u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e", + "LabelGithubWiki": "\u0412\u0438\u043a\u0438 \u043d\u0430 Github", + "LabelSwagger": "\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 Swagger", + "LabelStandard": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f", + "LabelViewApiDocumentation": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u043e API", + "LabelBrowseLibrary": "\u041e\u0431\u043e\u0437\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438", + "LabelConfigureMediaBrowser": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Media Browser", + "LabelOpenLibraryViewer": "\u0418\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0443", + "LabelRestartServer": "\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440", + "LabelShowLogWindow": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0416\u0443\u0440\u043d\u0430\u043b \u0432 \u043e\u043a\u043d\u0435", + "LabelPrevious": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435", + "LabelFinish": "\u0413\u043e\u0442\u043e\u0432\u043e", + "LabelNext": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435", + "LabelYoureDone": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e!", + "WelcomeToMediaBrowser": "\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u0432 Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "\u042d\u0442\u043e\u0442 \u043c\u0430\u0441\u0442\u0435\u0440 \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u0442 \u0432\u0430\u0441 \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438.", + "TellUsAboutYourself": "\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0438\u0442\u0435 \u043d\u0430\u043c \u043e \u0441\u0435\u0431\u0435", + "LabelYourFirstName": "\u0412\u0430\u0448\u0435 \u0438\u043c\u044f", + "MoreUsersCanBeAddedLater": "\u041f\u043e\u0437\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u041f\u0430\u043d\u0435\u043b\u044c.", + "UserProfilesIntro": "Media Browser \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438\u043c\u0435\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f.", + "LabelWindowsService": "\u0421\u043b\u0443\u0436\u0431\u0430 Windows", + "AWindowsServiceHasBeenInstalled": "\u0421\u043b\u0443\u0436\u0431\u0430 Windows \u0431\u044b\u043b\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430.", + "WindowsServiceIntro1": "Media Browser Server \u043e\u0431\u044b\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e \u0437\u043d\u0430\u0447\u043a\u043e\u043c \u0432 \u043b\u043e\u0442\u043a\u0435, \u043d\u043e \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u0435\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431\u0430\u043c\u0438 Windows.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b", + "LabelEnableVideoImageExtraction": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0438\u0437 \u0432\u0438\u0434\u0435\u043e", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0441\u0446\u0435\u043d \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u043c\u043e\u0432", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0432\u0442\u043e-\u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "\u041e\u041a", + "ButtonCancel": "\u041e\u0442\u043c\u0435\u043d\u0430", + "HeaderSetupLibrary": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u0430\u0448\u0435\u0439 \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438", + "ButtonAddMediaFolder": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0443", + "LabelFolderType": "\u0422\u0438\u043f \u043f\u0430\u043f\u043a\u0438:", + "MediaFolderHelpPluginRequired": "* \u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430, \u043d\u043f\u0440. GameBrowser \u0438\u043b\u0438 MB Bookshelf.", + "ReferToMediaLibraryWiki": "\u0421\u043c. \u0432 \u0432\u0438\u043a\u0438 \u043f\u043e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435 (media library)", + "LabelCountry": "\u0421\u0442\u0440\u0430\u043d\u0430", + "LabelLanguage": "\u042f\u0437\u044b\u043a:", + "HeaderPreferredMetadataLanguage": "\u041f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u044f\u0437\u044b\u043a \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445", + "LabelSaveLocalMetadata": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0430\u0445", + "LabelSaveLocalMetadataHelp": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0439 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432 \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0430\u0445, \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442 \u0438\u0445 \u0432 \u0442\u0430\u043a\u043e\u0435 \u043c\u0435\u0441\u0442\u043e, \u0433\u0434\u0435 \u043e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043b\u0435\u0433\u043a\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u044b.", + "LabelDownloadInternetMetadata": "\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442", + "LabelDownloadInternetMetadataHelp": "Media Browser \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e \u0432\u0430\u0448\u0435\u043c \u043c\u0435\u0434\u0438\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json new file mode 100644 index 000000000..9630509d3 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "Exit", + "LabelVisitCommunity": "Visit Community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "View Api Documentation", + "LabelBrowseLibrary": "Browse Library", + "LabelConfigureMediaBrowser": "Configure Media Browser", + "LabelOpenLibraryViewer": "Open Library Viewer", + "LabelRestartServer": "Restart Server", + "LabelShowLogWindow": "Show Log Window", + "LabelPrevious": "Previous", + "LabelFinish": "Finish", + "LabelNext": "Next", + "LabelYoureDone": "You're Done!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish</b> to view the <b>Dashboard</b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json new file mode 100644 index 000000000..6084c1511 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -0,0 +1,50 @@ +{ + "LabelExit": "\u96e2\u958b", + "LabelVisitCommunity": "\u8a2a\u554f\u793e\u5340", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "\u6a19\u6dee", + "LabelViewApiDocumentation": "View Api Documentation", + "LabelBrowseLibrary": "Browse Library", + "LabelConfigureMediaBrowser": "Configure Media Browser", + "LabelOpenLibraryViewer": "Open Library Viewer", + "LabelRestartServer": "\u91cd\u65b0\u555f\u52d5\u670d\u52d9\u5668", + "LabelShowLogWindow": "\u986f\u793a\u65e5\u8a8c\u8996\u7a97", + "LabelPrevious": "\u4e0a\u4e00\u500b", + "LabelFinish": "\u5b8c\u7d50", + "LabelNext": "\u4e0b\u4e00\u500b", + "LabelYoureDone": "You're Done!", + "WelcomeToMediaBrowser": "Welcome to Media Browser!", + "LabelMediaBrowser": "Media Browser", + "ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.", + "TellUsAboutYourself": "Tell us about yourself", + "LabelYourFirstName": "Your first name:", + "MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.", + "UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "LabelWindowsService": "Windows Service", + "AWindowsServiceHasBeenInstalled": "A Windows Service has been installed.", + "WindowsServiceIntro1": "Media Browser Server normally runs as a desktop application with a tray icon, but if you prefer to run it as a background service, it can be started from the windows services control panel instead.", + "WindowsServiceIntro2": "If using the windows service, please note that it cannot be run at the same time as the tray icon, so you'll need to exit the tray in order to run the service. The service will also need to be configured with administrative privileges via the control panel. Please note that at this time the service is unable to self-update, so new versions will require manual interaction.", + "WizardCompleted": "That's all we need for now. Media Browser has begun collecting information about your media library. Check out some of our apps, and then click <b>Finish<\/b> to view the <b>Dashboard<\/b>.", + "LabelConfigureSettings": "Configure settings", + "LabelEnableVideoImageExtraction": "Enable video image extraction", + "VideoImageExtractionHelp": "For videos that don't already have images, and that we're unable to find internet images for. This will add some additional time to the initial library scan but will result in a more pleasing presentation.", + "LabelEnableChapterImageExtractionForMovies": "Extract chapter image extraction for Movies", + "LabelChapterImageExtractionForMoviesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "LabelEnableAutomaticPortMapping": "Enable automatic port mapping", + "LabelEnableAutomaticPortMappingHelp": "UPnP allows automated router configuration for easy remote access. This may not work with some router models.", + "ButtonOk": "Ok", + "ButtonCancel": "Cancel", + "HeaderSetupLibrary": "Setup your media library", + "ButtonAddMediaFolder": "Add media folder", + "LabelFolderType": "Folder type:", + "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.", + "ReferToMediaLibraryWiki": "Refer to the media library wiki.", + "LabelCountry": "Country:", + "LabelLanguage": "Language:", + "HeaderPreferredMetadataLanguage": "Preferred metadata language:", + "LabelSaveLocalMetadata": "Save artwork and metadata into media folders", + "LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.", + "LabelDownloadInternetMetadata": "Download artwork and metadata from the internet", + "LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations." +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index ea7ef2ed6..d06cf0181 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -141,6 +141,7 @@ <Compile Include="IO\LibraryMonitor.cs" /> <Compile Include="Library\CoreResolutionIgnoreRule.cs" /> <Compile Include="Library\LibraryManager.cs" /> + <Compile Include="Library\MusicManager.cs" /> <Compile Include="Library\Resolvers\PhotoResolver.cs" /> <Compile Include="Library\SearchEngine.cs" /> <Compile Include="Library\ResolverHelper.cs" /> @@ -281,6 +282,25 @@ <EmbeddedResource Include="Localization\Ratings\ru.txt" /> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="Localization\JavaScript\javascript.json" /> + <EmbeddedResource Include="Localization\Server\server.json" /> + <EmbeddedResource Include="Localization\Server\de.json" /> + <EmbeddedResource Include="Localization\Server\pt_PT.json" /> + <EmbeddedResource Include="Localization\Server\ru.json" /> + <EmbeddedResource Include="Localization\JavaScript\en_US.json" /> + <EmbeddedResource Include="Localization\JavaScript\pt_PT.json" /> + <EmbeddedResource Include="Localization\Server\en_US.json" /> + <EmbeddedResource Include="Localization\JavaScript\de.json" /> + <EmbeddedResource Include="Localization\JavaScript\fr.json" /> + <EmbeddedResource Include="Localization\JavaScript\nl.json" /> + <EmbeddedResource Include="Localization\JavaScript\ru.json" /> + <EmbeddedResource Include="Localization\JavaScript\zh_TW.json" /> + <EmbeddedResource Include="Localization\Server\fr.json" /> + <EmbeddedResource Include="Localization\Server\nl.json" /> + <EmbeddedResource Include="Localization\Server\zh_TW.json" /> + <EmbeddedResource Include="Localization\JavaScript\es.json" /> + <EmbeddedResource Include="Localization\Server\es.json" /> + <EmbeddedResource Include="Localization\Server\pt_BR.json" /> <None Include="packages.config" /> </ItemGroup> <ItemGroup> @@ -385,6 +405,7 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> + <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs index d806db1e0..608e92b24 100644 --- a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs +++ b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Roku public bool SupportsMediaRemoteControl { - get { return true; } + get { return false; } } public bool IsSessionActive @@ -41,16 +41,6 @@ namespace MediaBrowser.Server.Implementations.Roku } } - public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken) - { - return SendCommand(new WebSocketMessage<string> - { - MessageType = "SystemCommand", - Data = command.ToString() - - }, cancellationToken); - } - public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken) { return SendCommand(new WebSocketMessage<MessageCommand> @@ -148,9 +138,9 @@ namespace MediaBrowser.Server.Implementations.Roku } - public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken) + public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { - return SendCommand(new WebSocketMessage<GenericCommand> + return SendCommand(new WebSocketMessage<GeneralCommand> { MessageType = "Command", Data = command diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 9d405a175..3b6e9fefb 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -41,6 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; + private readonly IMusicManager _musicManager; /// <summary> /// Gets or sets the configuration manager. @@ -564,7 +565,7 @@ namespace MediaBrowser.Server.Implementations.Session return playedToCompletion; } - + /// <summary> /// Updates playstate position for an item but does not save /// </summary> @@ -660,38 +661,59 @@ namespace MediaBrowser.Server.Implementations.Session return session; } - public Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken) + public Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - - return session.SessionController.SendSystemCommand(command, cancellationToken); + + return session.SessionController.SendMessageCommand(command, cancellationToken); } - public Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken) + public Task SendGeneralCommand(Guid controllingSessionId, Guid sessionId, GeneralCommand command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - - return session.SessionController.SendMessageCommand(command, cancellationToken); + + return session.SessionController.SendGeneralCommand(command, cancellationToken); } public Task SendPlayCommand(Guid controllingSessionId, Guid sessionId, PlayRequest command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); - var items = command.ItemIds.Select(i => _libraryManager.GetItemById(new Guid(i))) - .Where(i => i.LocationType != LocationType.Virtual) - .ToList(); + var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null; - if (session.UserId.HasValue) + List<BaseItem> items; + + if (command.PlayCommand == PlayCommand.PlayInstantMix) { - var user = _userManager.GetUserById(session.UserId.Value); + items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user)) + .Where(i => i.LocationType != LocationType.Virtual) + .ToList(); + command.PlayCommand = PlayCommand.PlayNow; + } + else + { + items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user)) + .Where(i => i.LocationType != LocationType.Virtual) + .ToList(); + } + + if (command.PlayCommand == PlayCommand.PlayShuffle) + { + items = items.OrderBy(i => Guid.NewGuid()).ToList(); + command.PlayCommand = PlayCommand.PlayNow; + } + + command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(); + + if (user != null) + { if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) { throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name)); @@ -723,13 +745,69 @@ namespace MediaBrowser.Server.Implementations.Session return session.SessionController.SendPlayCommand(command, cancellationToken); } + private IEnumerable<BaseItem> TranslateItemForPlayback(string id, User user) + { + var item = _libraryManager.GetItemById(new Guid(id)); + + if (item.IsFolder) + { + var folder = (Folder)item; + + var items = user == null ? folder.RecursiveChildren : + folder.GetRecursiveChildren(user); + + items = items.Where(i => !i.IsFolder); + + items = items.OrderBy(i => i.SortName); + + return items; + } + + return new[] { item }; + } + + private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user) + { + var item = _libraryManager.GetItemById(new Guid(id)); + + var audio = item as Audio; + + if (audio != null) + { + return _musicManager.GetInstantMixFromSong(audio, user); + } + + var artist = item as MusicArtist; + + if (artist != null) + { + return _musicManager.GetInstantMixFromArtist(artist.Name, user); + } + + var album = item as MusicAlbum; + + if (album != null) + { + return _musicManager.GetInstantMixFromAlbum(album, user); + } + + var genre = item as MusicGenre; + + if (genre != null) + { + return _musicManager.GetInstantMixFromGenres(new[] { genre.Name }, user); + } + + return new BaseItem[] { }; + } + public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - + return session.SessionController.SendBrowseCommand(command, cancellationToken); } diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index ddf4ec2ca..3bb84fa0e 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -57,18 +57,6 @@ namespace MediaBrowser.Server.Implementations.Session return socket; } - public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken) - { - var socket = GetActiveSocket(); - - return socket.SendAsync(new WebSocketMessage<string> - { - MessageType = "SystemCommand", - Data = command.ToString() - - }, cancellationToken); - } - public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken) { var socket = GetActiveSocket(); @@ -199,13 +187,13 @@ namespace MediaBrowser.Server.Implementations.Session }, cancellationToken); } - public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken) + public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { var socket = GetActiveSocket(); - return socket.SendAsync(new WebSocketMessage<GenericCommand> + return socket.SendAsync(new WebSocketMessage<GeneralCommand> { - MessageType = "Command", + MessageType = "GeneralCommand", Data = command }, cancellationToken); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index b7e9017d6..b545156ec 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.ServerApplication private ILiveTvManager LiveTvManager { get; set; } - private ILocalizationManager LocalizationManager { get; set; } + internal ILocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } private IChannelManager ChannelManager { get; set; } @@ -421,6 +421,9 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(ServerConfigurationManager); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer); + RegisterSingleInstance(LocalizationManager); + RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger)); RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer()); @@ -449,6 +452,8 @@ namespace MediaBrowser.ServerApplication LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager); RegisterSingleInstance(LibraryManager); + RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager)); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(LibraryMonitor); @@ -470,9 +475,6 @@ namespace MediaBrowser.ServerApplication ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager); RegisterSingleInstance(ServerManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager); - RegisterSingleInstance(LocalizationManager); - var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15)); @@ -503,7 +505,7 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance<IAppThemeManager>(appThemeManager); - var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA")); + var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer); RegisterSingleInstance<IDlnaManager>(dlnaManager); var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); @@ -877,6 +879,7 @@ namespace MediaBrowser.ServerApplication ProgramDataPath = ApplicationPaths.ProgramDataPath, LogPath = ApplicationPaths.LogDirectoryPath, ItemsByNamePath = ApplicationPaths.ItemsByNamePath, + InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, MacAddress = GetMacAddress(), HttpServerPortNumber = HttpServerPort, diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs index 6c40b549b..8e9091906 100644 --- a/MediaBrowser.ServerApplication/LibraryViewer.cs +++ b/MediaBrowser.ServerApplication/LibraryViewer.cs @@ -16,18 +16,16 @@ namespace MediaBrowser.ServerApplication { private readonly IJsonSerializer _jsonSerializer; private readonly ILibraryManager _libraryManager; - private readonly IDisplayPreferencesRepository _displayPreferencesManager; private readonly IItemRepository _itemRepository; private User _currentUser; - public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo) + public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo) { InitializeComponent(); _jsonSerializer = jsonSerializer; _libraryManager = libraryManager; - _displayPreferencesManager = displayPreferencesManager; _itemRepository = itemRepo; foreach (var user in userManager.Users) @@ -44,7 +42,7 @@ namespace MediaBrowser.ServerApplication if (e.Node != null) { var item = (BaseItem)e.Node.Tag; - lblType.Text = "Type: " + item.GetType().Name; + lblType.Text = item.GetType().Name; var json = FormatJson(_jsonSerializer.SerializeToString(item)); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 3d490a1f8..d8bd3938e 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.ServerApplication { //Application.EnableVisualStyles(); //Application.SetCompatibleTextRenderingDefault(false); - _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository); + _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository, _appHost.LocalizationManager); Application.Run(); } diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index 99b6db2d7..27f47787f 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -4,6 +4,7 @@ using System.Windows.Forms; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -41,6 +42,7 @@ namespace MediaBrowser.ServerApplication private readonly IJsonSerializer _jsonSerializer; private readonly IDisplayPreferencesRepository _displayPreferencesManager; private readonly IItemRepository _itemRepository; + private readonly ILocalizationManager _localization; private LogForm _logForm; public bool Visible @@ -56,10 +58,17 @@ namespace MediaBrowser.ServerApplication } } - public ServerNotifyIcon(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo) + public ServerNotifyIcon(ILogManager logManager, + IServerApplicationHost appHost, + IServerConfigurationManager configurationManager, + IUserManager userManager, ILibraryManager libraryManager, + IJsonSerializer jsonSerializer, + IDisplayPreferencesRepository displayPreferencesManager, + IItemRepository itemRepo, ILocalizationManager localization) { _logger = logManager.GetLogger("MainWindow"); _itemRepository = itemRepo; + _localization = localization; _appHost = appHost; _logManager = logManager; _configurationManager = configurationManager; @@ -118,20 +127,17 @@ namespace MediaBrowser.ServerApplication // cmdExit.Name = "cmdExit"; cmdExit.Size = new System.Drawing.Size(208, 22); - cmdExit.Text = "Exit"; // // cmdCommunity // cmdCommunity.Name = "cmdCommunity"; cmdCommunity.Size = new System.Drawing.Size(208, 22); - cmdCommunity.Text = "Visit Community"; // // cmdLogWindow // cmdLogWindow.CheckOnClick = true; cmdLogWindow.Name = "cmdLogWindow"; cmdLogWindow.Size = new System.Drawing.Size(208, 22); - cmdLogWindow.Text = "Show Log Window"; // // toolStripSeparator1 // @@ -142,13 +148,11 @@ namespace MediaBrowser.ServerApplication // cmdRestart.Name = "cmdRestart"; cmdRestart.Size = new System.Drawing.Size(208, 22); - cmdRestart.Text = "Restart Server"; // // cmdLibraryExplorer // cmdLibraryExplorer.Name = "cmdLibraryExplorer"; cmdLibraryExplorer.Size = new System.Drawing.Size(208, 22); - cmdLibraryExplorer.Text = "Open Library Explorer"; // // toolStripSeparator2 // @@ -159,13 +163,11 @@ namespace MediaBrowser.ServerApplication // cmdConfigure.Name = "cmdConfigure"; cmdConfigure.Size = new System.Drawing.Size(208, 22); - cmdConfigure.Text = "Configure Media Browser"; // // cmdBrowse // cmdBrowse.Name = "cmdBrowse"; cmdBrowse.Size = new System.Drawing.Size(208, 22); - cmdBrowse.Text = "Browse Library"; // // cmdApiDocs // @@ -175,25 +177,21 @@ namespace MediaBrowser.ServerApplication cmdGtihub}); cmdApiDocs.Name = "cmdApiDocs"; cmdApiDocs.Size = new System.Drawing.Size(208, 22); - cmdApiDocs.Text = "View Api Documentation"; // // cmdStandardDocs // cmdStandardDocs.Name = "cmdStandardDocs"; cmdStandardDocs.Size = new System.Drawing.Size(136, 22); - cmdStandardDocs.Text = "Standard"; // // cmdSwagger // cmdSwagger.Name = "cmdSwagger"; cmdSwagger.Size = new System.Drawing.Size(136, 22); - cmdSwagger.Text = "Swagger"; // // cmdGtihub // cmdGtihub.Name = "cmdGtihub"; cmdGtihub.Size = new System.Drawing.Size(136, 22); - cmdGtihub.Text = "Github Wiki"; cmdExit.Click += cmdExit_Click; cmdRestart.Click += cmdRestart_Click; @@ -211,6 +209,8 @@ namespace MediaBrowser.ServerApplication _logManager.LoggerLoaded += LoadLogWindow; _configurationManager.ConfigurationUpdated += Instance_ConfigurationUpdated; + LocalizeText(); + if (_appHost.IsFirstRun) { Action action = () => notifyIcon1.ShowBalloonTip(5000, "Media Browser", "Welcome to Media Browser Server!", ToolTipIcon.Info); @@ -219,6 +219,24 @@ namespace MediaBrowser.ServerApplication } } + private void LocalizeText() + { + _uiCulture = _configurationManager.Configuration.UICulture; + + cmdExit.Text = _localization.GetLocalizedString("LabelExit"); + cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity"); + cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki"); + cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger"); + cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard"); + cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation"); + cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary"); + cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser"); + cmdLibraryExplorer.Text = _localization.GetLocalizedString("LabelOpenLibraryViewer"); + cmdRestart.Text = _localization.GetLocalizedString("LabelRestartServer"); + cmdLogWindow.Text = _localization.GetLocalizedString("LabelShowLogWindow"); + } + + private string _uiCulture; /// <summary> /// Handles the ConfigurationUpdated event of the Instance control. /// </summary> @@ -226,6 +244,12 @@ namespace MediaBrowser.ServerApplication /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> void Instance_ConfigurationUpdated(object sender, EventArgs e) { + if (!string.Equals(_configurationManager.Configuration.UICulture, _uiCulture, + StringComparison.OrdinalIgnoreCase)) + { + LocalizeText(); + } + Action action = () => { var isLogWindowOpen = _logForm != null; @@ -307,7 +331,7 @@ namespace MediaBrowser.ServerApplication void cmdLibraryExplorer_Click(object sender, EventArgs e) { - new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _displayPreferencesManager, _itemRepository).Show(); + new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show(); } void cmdRestart_Click(object sender, EventArgs e) diff --git a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs index e9ba17bc1..ef3b79699 100644 --- a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs +++ b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs @@ -121,9 +121,9 @@ this.lblStatus.Location = new System.Drawing.Point(3, 0); this.lblStatus.MaximumSize = new System.Drawing.Size(0, 100); this.lblStatus.Name = "lblStatus"; - this.lblStatus.Size = new System.Drawing.Size(599, 59); + this.lblStatus.Size = new System.Drawing.Size(469, 59); this.lblStatus.TabIndex = 0; - this.lblStatus.Text = "Loading Media Browser Server"; + this.lblStatus.Text = "Loading Media Browser"; this.lblStatus.UseWaitCursor = true; // // panel1 diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 99afbbdd7..de113297e 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -3,9 +3,11 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using ServiceStack; using ServiceStack.Web; using System; @@ -15,6 +17,8 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using WebMarkupMin.Core.Minifiers; +using WebMarkupMin.Core.Settings; namespace MediaBrowser.WebDashboard.Api { @@ -96,6 +100,8 @@ namespace MediaBrowser.WebDashboard.Api private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + private readonly IJsonSerializer _jsonSerializer; /// <summary> /// Initializes a new instance of the <see cref="DashboardService" /> class. @@ -103,11 +109,13 @@ namespace MediaBrowser.WebDashboard.Api /// <param name="appHost">The app host.</param> /// <param name="serverConfigurationManager">The server configuration manager.</param> /// <param name="fileSystem">The file system.</param> - public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem) + public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; + _localization = localization; + _jsonSerializer = jsonSerializer; } /// <summary> @@ -148,7 +156,7 @@ namespace MediaBrowser.WebDashboard.Api { var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); - return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream())); + return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream(), null)); } /// <summary> @@ -210,13 +218,16 @@ namespace MediaBrowser.WebDashboard.Api var contentType = MimeTypes.GetMimeType(path); + var isHtml = IsHtml(path); + var localizationCulture = GetLocalizationCulture(); + // Don't cache if not configured to do so // But always cache images to simulate production - if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && - !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && + if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && + !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) { - return ResultFactory.GetResult(GetResourceStream(path).Result, contentType); + return ResultFactory.GetResult(GetResourceStream(path, isHtml, localizationCulture).Result, contentType); } TimeSpan? cacheDuration = null; @@ -230,17 +241,24 @@ namespace MediaBrowser.WebDashboard.Api var assembly = GetType().Assembly.GetName(); - var cacheKey = (assembly.Version + path).GetMD5(); + var cacheKey = (assembly.Version + (localizationCulture ?? string.Empty) + path).GetMD5(); - return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path)); + return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, isHtml, localizationCulture)); + } + + private string GetLocalizationCulture() + { + return _serverConfigurationManager.Configuration.UICulture; } /// <summary> /// Gets the resource stream. /// </summary> /// <param name="path">The path.</param> + /// <param name="isHtml">if set to <c>true</c> [is HTML].</param> + /// <param name="localizationCulture">The localization culture.</param> /// <returns>Task{Stream}.</returns> - private async Task<Stream> GetResourceStream(string path) + private async Task<Stream> GetResourceStream(string path, bool isHtml, string localizationCulture) { Stream resourceStream; @@ -259,13 +277,11 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null) { - var isHtml = IsHtml(path); - // Don't apply any caching for html pages // jQuery ajax doesn't seem to handle if-modified-since correctly if (isHtml) { - resourceStream = await ModifyHtml(resourceStream).ConfigureAwait(false); + resourceStream = await ModifyHtml(resourceStream, localizationCulture).ConfigureAwait(false); } } @@ -297,26 +313,48 @@ namespace MediaBrowser.WebDashboard.Api /// </summary> /// <param name="sourceStream">The source stream.</param> /// <returns>Task{Stream}.</returns> - internal async Task<Stream> ModifyHtml(Stream sourceStream) + private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture) { - string html; - - using (var memoryStream = new MemoryStream()) + using (sourceStream) { - await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); + string html; - html = Encoding.UTF8.GetString(memoryStream.ToArray()); - } + using (var memoryStream = new MemoryStream()) + { + await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); + + html = Encoding.UTF8.GetString(memoryStream.ToArray()); - var version = GetType().Assembly.GetName().Version; + if (!string.IsNullOrWhiteSpace(localizationCulture)) + { + html = _localization.LocalizeDocument(html, localizationCulture, GetLocalizationToken); + } - html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version)); + try + { + var minifier = new HtmlMinifier(new HtmlMinificationSettings(true)); - var bytes = Encoding.UTF8.GetBytes(html); + html = minifier.Minify(html).MinifiedContent; + } + catch (Exception ex) + { + Logger.ErrorException("Error minifying html", ex); + } + } + + var version = GetType().Assembly.GetName().Version; + + html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version)); - sourceStream.Dispose(); + var bytes = Encoding.UTF8.GetBytes(html); - return new MemoryStream(bytes); + return new MemoryStream(bytes); + } + } + + private string GetLocalizationToken(string phrase) + { + return "${" + phrase + "}"; } /// <summary> @@ -376,8 +414,8 @@ namespace MediaBrowser.WebDashboard.Api var files = new[] { "scripts/all.js" + versionString, - "thirdparty/jstree1.0/jquery.jstree.min.js" - //"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js" + "thirdparty/jstree1.0/jquery.jstree.min.js", + "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js" }; var tags = files.Select(s => string.Format("<script src=\"{0}\"></script>", s)).ToArray(); @@ -393,154 +431,204 @@ namespace MediaBrowser.WebDashboard.Api /// <returns>Task{Stream}.</returns> private async Task<Stream> GetAllJavascript() { - var scriptFiles = new[] - { - "extensions.js", - "site.js", - "librarybrowser.js", - "librarylist.js", - "editorsidebar.js", - "librarymenu.js", - //"chromecast.js", - "contextmenu.js", - - "ratingdialog.js", - "aboutpage.js", - "allusersettings.js", - "alphapicker.js", - "addpluginpage.js", - "advancedconfigurationpage.js", - "advancedpaths.js", - "advancedserversettings.js", - "metadataadvanced.js", - "appsplayback.js", - "appsweather.js", - "autoorganizetv.js", - "autoorganizelog.js", - "channels.js", - "channelitems.js", - "dashboardinfo.js", - "dashboardpage.js", - "directorybrowser.js", - "dlnaprofile.js", - "dlnaprofiles.js", - "dlnasettings.js", - "editcollectionitems.js", - "edititemmetadata.js", - "edititempeople.js", - "edititemimages.js", - "encodingsettings.js", - "gamesrecommendedpage.js", - "gamesystemspage.js", - "gamespage.js", - "gamegenrepage.js", - "gamestudiospage.js", - "indexpage.js", - "itembynamedetailpage.js", - "itemdetailpage.js", - "itemgallery.js", - "itemlistpage.js", - "librarypathmapping.js", - "libraryreport.js", - "librarysettings.js", - "livetvchannel.js", - "livetvchannels.js", - "livetvguide.js", - "livetvnewrecording.js", - "livetvprogram.js", - "livetvrecording.js", - "livetvrecordinglist.js", - "livetvrecordings.js", - "livetvtimer.js", - "livetvseriestimer.js", - "livetvseriestimers.js", - "livetvsettings.js", - "livetvsuggested.js", - "livetvstatus.js", - "livetvtimers.js", - "loginpage.js", - "logpage.js", - "medialibrarypage.js", - "mediaplayer.js", - - "mediaplayer-video.js", - - "metadataconfigurationpage.js", - "metadataimagespage.js", - "moviegenres.js", - "moviecollections.js", - "movies.js", - "movieslatest.js", - "moviepeople.js", - "moviesrecommended.js", - "moviestudios.js", - "movietrailers.js", - "musicalbums.js", - "musicalbumartists.js", - "musicartists.js", - "musicgenres.js", - "musicrecommended.js", - "musicvideos.js", - "notifications.js", - "playlist.js", - "plugincatalogpage.js", - "pluginspage.js", - "pluginupdatespage.js", - "remotecontrol.js", - "scheduledtaskpage.js", - "scheduledtaskspage.js", - "search.js", - "songs.js", - "supporterkeypage.js", - "supporterpage.js", - "episodes.js", - "tvgenres.js", - "tvlatest.js", - "tvpeople.js", - "tvrecommended.js", - "tvshows.js", - "tvstudios.js", - "tvupcoming.js", - "useredit.js", - "userpassword.js", - "userimagepage.js", - "userprofilespage.js", - "usersettings.js", - "userparentalcontrol.js", - "wizardfinishpage.js", - "wizardimagesettings.js", - "wizardservice.js", - "wizardstartpage.js", - "wizardsettings.js", - "wizarduserpage.js" - }; - var memoryStream = new MemoryStream(); var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); + // jQuery + jQuery mobile await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.js", newLineBytes).ConfigureAwait(false); + await AppendLocalization(memoryStream).ConfigureAwait(false); + await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); + + // Write the version string for the dashboard comparison function var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion); var versionBytes = Encoding.UTF8.GetBytes(versionString); await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); - await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false); - + var builder = new StringBuilder(); var assembly = GetType().Assembly; - await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false); + using (var stream = assembly.GetManifestResourceStream("MediaBrowser.WebDashboard.ApiClient.js")) + { + using (var streamReader = new StreamReader(stream)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } + + foreach (var file in GetScriptFiles()) + { + var path = GetDashboardResourcePath("scripts/" + file); + + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + { + using (var streamReader = new StreamReader(fs)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } + } + + var js = builder.ToString(); + + try + { + var result = new CrockfordJsMinifier().Minify(js, false, Encoding.UTF8); - foreach (var file in scriptFiles) + js = result.MinifiedContent; + } + catch (Exception ex) { - await AppendResource(memoryStream, "scripts/" + file, newLineBytes).ConfigureAwait(false); + Logger.ErrorException("Error minifying javascript", ex); } + var bytes = Encoding.UTF8.GetBytes(js); + await memoryStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + memoryStream.Position = 0; return memoryStream; } + private IEnumerable<string> GetScriptFiles() + { + return new[] + { + "extensions.js", + "site.js", + "librarybrowser.js", + "librarylist.js", + "editorsidebar.js", + "librarymenu.js", + "chromecast.js", + "contextmenu.js", + + "mediacontroller.js", + "mediaplayer.js", + "mediaplayer-video.js", + + "ratingdialog.js", + "aboutpage.js", + "allusersettings.js", + "alphapicker.js", + "addpluginpage.js", + "advancedconfigurationpage.js", + "advancedpaths.js", + "advancedserversettings.js", + "metadataadvanced.js", + "appsplayback.js", + "appsweather.js", + "autoorganizetv.js", + "autoorganizelog.js", + "channels.js", + "channelitems.js", + "dashboardgeneral.js", + "dashboardinfo.js", + "dashboardpage.js", + "directorybrowser.js", + "dlnaprofile.js", + "dlnaprofiles.js", + "dlnasettings.js", + "editcollectionitems.js", + "edititemmetadata.js", + "edititempeople.js", + "edititemimages.js", + "encodingsettings.js", + "gamesrecommendedpage.js", + "gamesystemspage.js", + "gamespage.js", + "gamegenrepage.js", + "gamestudiospage.js", + "indexpage.js", + "itembynamedetailpage.js", + "itemdetailpage.js", + "itemgallery.js", + "itemlistpage.js", + "librarypathmapping.js", + "libraryreport.js", + "librarysettings.js", + "livetvchannel.js", + "livetvchannels.js", + "livetvguide.js", + "livetvnewrecording.js", + "livetvprogram.js", + "livetvrecording.js", + "livetvrecordinglist.js", + "livetvrecordings.js", + "livetvtimer.js", + "livetvseriestimer.js", + "livetvseriestimers.js", + "livetvsettings.js", + "livetvsuggested.js", + "livetvstatus.js", + "livetvtimers.js", + "loginpage.js", + "logpage.js", + "medialibrarypage.js", + "metadataconfigurationpage.js", + "metadataimagespage.js", + "moviegenres.js", + "moviecollections.js", + "movies.js", + "movieslatest.js", + "moviepeople.js", + "moviesrecommended.js", + "moviestudios.js", + "movietrailers.js", + "musicalbums.js", + "musicalbumartists.js", + "musicartists.js", + "musicgenres.js", + "musicrecommended.js", + "musicvideos.js", + "notifications.js", + "playlist.js", + "plugincatalogpage.js", + "pluginspage.js", + "pluginupdatespage.js", + "remotecontrol.js", + "scheduledtaskpage.js", + "scheduledtaskspage.js", + "search.js", + "songs.js", + "supporterkeypage.js", + "supporterpage.js", + "episodes.js", + "tvgenres.js", + "tvlatest.js", + "tvpeople.js", + "tvrecommended.js", + "tvshows.js", + "tvstudios.js", + "tvupcoming.js", + "useredit.js", + "userpassword.js", + "userimagepage.js", + "userprofilespage.js", + "usersettings.js", + "userparentalcontrol.js", + "wizardfinishpage.js", + "wizardimagesettings.js", + "wizardservice.js", + "wizardstartpage.js", + "wizardsettings.js", + "wizarduserpage.js" + }; + } + + private async Task AppendLocalization(Stream stream) + { + var js = "window.localizationGlossary=" + _jsonSerializer.SerializeToString(_localization.GetJavaScriptLocalizationDictionary(GetLocalizationCulture())); + + var bytes = Encoding.UTF8.GetBytes(js); + await stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + /// <summary> /// Gets all CSS. /// </summary> @@ -568,35 +656,40 @@ namespace MediaBrowser.WebDashboard.Api "icons.css" }; - var memoryStream = new MemoryStream(); - - var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); + var builder = new StringBuilder(); foreach (var file in files) { - await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false); + var path = GetDashboardResourcePath("css/" + file); + + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + { + using (var streamReader = new StreamReader(fs)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } } - - memoryStream.Position = 0; - return memoryStream; - } - /// <summary> - /// Appends the resource. - /// </summary> - /// <param name="assembly">The assembly.</param> - /// <param name="outputStream">The output stream.</param> - /// <param name="path">The path.</param> - /// <param name="newLineBytes">The new line bytes.</param> - /// <returns>Task.</returns> - private async Task AppendResource(Assembly assembly, Stream outputStream, string path, byte[] newLineBytes) - { - using (var stream = assembly.GetManifestResourceStream(path)) - { - await stream.CopyToAsync(outputStream).ConfigureAwait(false); + var css = builder.ToString(); - await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); - } + //try + //{ + // var result = new KristensenCssMinifier().Minify(builder.ToString(), false, Encoding.UTF8); + + // css = result.MinifiedContent; + //} + //catch (Exception ex) + //{ + // Logger.ErrorException("Error minifying css", ex); + //} + + var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(css)); + + memoryStream.Position = 0; + return memoryStream; } /// <summary> diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index ea1905bad..f268c44ec 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -4058,7 +4058,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi options.ForceTitle = true; } - var url = self.getUrl("Packages/" + packageId + "Reviews", options); + var url = self.getUrl("Packages/" + packageId + "/Reviews", options); return self.ajax({ type: "GET", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 6a8cc49b1..0ddb05577 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -57,6 +57,9 @@ <Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="WebMarkupMin.Core">
+ <HintPath>..\packages\WebMarkupMin.Core.0.8.18\lib\net40\WebMarkupMin.Core.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -221,6 +224,9 @@ <Content Include="dashboard-ui\css\mediaplayer.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\dashboardgeneral.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\dashboardinfopage.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -515,6 +521,9 @@ <Content Include="dashboard-ui\scripts\contextmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\dashboardinfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -587,6 +596,9 @@ <Content Include="dashboard-ui\scripts\editorsidebar.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\mediacontroller.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\mediaplayer-video.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -638,9 +650,6 @@ <Content Include="dashboard-ui\livetvseriestimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1474,9 +1483,6 @@ <Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1967,6 +1973,7 @@ </Content>
</ItemGroup>
<ItemGroup>
+ <None Include="app.config" />
<None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -1980,6 +1987,9 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
+ <None Include="WebMarkupMin.Configuration.xsd">
+ <SubType>Designer</SubType>
+ </None>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/MediaBrowser.WebDashboard/app.config b/MediaBrowser.WebDashboard/app.config new file mode 100644 index 000000000..8c2c664cd --- /dev/null +++ b/MediaBrowser.WebDashboard/app.config @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <configSections> + <sectionGroup name="webMarkupMin"> + <section name="core" type="WebMarkupMin.Core.Configuration.CoreConfiguration, WebMarkupMin.Core" /> + </sectionGroup> + </configSections> + <webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd"> + <core> + <css> + <minifiers> + <add name="NullCssMinifier" displayName="Null CSS Minifier" type="WebMarkupMin.Core.Minifiers.NullCssMinifier, WebMarkupMin.Core" /> + <add name="KristensenCssMinifier" displayName="Mads Kristensen's CSS minifier" type="WebMarkupMin.Core.Minifiers.KristensenCssMinifier, WebMarkupMin.Core" /> + </minifiers> + </css> + <js> + <minifiers> + <add name="NullJsMinifier" displayName="Null JS Minifier" type="WebMarkupMin.Core.Minifiers.NullJsMinifier, WebMarkupMin.Core" /> + <add name="CrockfordJsMinifier" displayName="Douglas Crockford's JS Minifier" type="WebMarkupMin.Core.Minifiers.CrockfordJsMinifier, WebMarkupMin.Core" /> + </minifiers> + </js> + <logging> + <loggers> + <add name="NullLogger" displayName="Null Logger" type="WebMarkupMin.Core.Loggers.NullLogger, WebMarkupMin.Core" /> + <add name="ThrowExceptionLogger" displayName="Throw exception logger" type="WebMarkupMin.Core.Loggers.ThrowExceptionLogger, WebMarkupMin.Core" /> + </loggers> + </logging> + </core> + </webMarkupMin> +</configuration>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index a5074d3c8..b867f7a92 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.248" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" /> + <package id="WebMarkupMin.Core" version="0.8.18" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index a3b4533dd..e4deb4a0b 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.346</version> + <version>3.0.347</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.346" /> + <dependency id="MediaBrowser.Common" version="3.0.347" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.1" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index b80f673e3..f60e9d429 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.346</version> + <version>3.0.347</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index eccfcccd2..f73cde199 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.346</version> + <version>3.0.347</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.346" /> + <dependency id="MediaBrowser.Common" version="3.0.347" /> </dependencies> </metadata> <files> |
